mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 05:15:10 +00:00
Compare commits
7 Commits
97788b4e07
...
8a0f000bab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a0f000bab | ||
|
|
2ffeb49acb | ||
|
|
5fec753fb9 | ||
|
|
acbaff7bb7 | ||
|
|
706323dc3e | ||
|
|
b0804d939c | ||
|
|
d0ac452405 |
@@ -388,6 +388,10 @@ def main():
|
||||
for ind, i in enumerate(resource_edge_info[::-1]):
|
||||
source_node: ResourceDict = nodes[i["source"]]
|
||||
target_node: ResourceDict = nodes[i["target"]]
|
||||
if "sourceHandle" not in source_node:
|
||||
continue
|
||||
if "targetHandle" not in target_node:
|
||||
continue
|
||||
source_handle = i["sourceHandle"]
|
||||
target_handle = i["targetHandle"]
|
||||
source_handler_keys = [
|
||||
|
||||
@@ -300,6 +300,10 @@ class HTTPClient:
|
||||
)
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
|
||||
if response.status_code == 200:
|
||||
res = response.json()
|
||||
if "code" in res and res["code"] != 0:
|
||||
logger.error(f"注册资源失败: {response.text}")
|
||||
return response
|
||||
|
||||
def request_startup_json(self) -> Optional[Dict[str, Any]]:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple, Union, Optional, Any, List
|
||||
|
||||
from opcua import Client, Node
|
||||
from opcua import Client, Node, ua
|
||||
from opcua.ua import NodeId, NodeClass, VariantType
|
||||
|
||||
|
||||
@@ -47,23 +47,68 @@ class Base(ABC):
|
||||
def _get_node(self) -> Node:
|
||||
if self._node is None:
|
||||
try:
|
||||
# 检查是否是NumericNodeId(ns=X;i=Y)格式
|
||||
if "NumericNodeId" in self._node_id:
|
||||
# 从字符串中提取命名空间和标识符
|
||||
import re
|
||||
match = re.search(r'ns=(\d+);i=(\d+)', self._node_id)
|
||||
if match:
|
||||
ns = int(match.group(1))
|
||||
identifier = int(match.group(2))
|
||||
node_id = NodeId(identifier, ns)
|
||||
self._node = self._client.get_node(node_id)
|
||||
# 尝试多种 NodeId 字符串格式解析,兼容不同服务器/库的输出
|
||||
# 可能的格式示例: 'ns=2;i=1234', 'ns=2;s=SomeString',
|
||||
# 'StringNodeId(ns=4;s=OPC|变量名)', 'NumericNodeId(ns=2;i=1234)' 等
|
||||
import re
|
||||
|
||||
nid = self._node_id
|
||||
# 如果已经是 NodeId/Node 对象(库用户可能传入),直接使用
|
||||
try:
|
||||
from opcua.ua import NodeId as UaNodeId
|
||||
if isinstance(nid, UaNodeId):
|
||||
self._node = self._client.get_node(nid)
|
||||
return self._node
|
||||
except Exception:
|
||||
# 若导入或类型判断失败,则继续下一步
|
||||
pass
|
||||
|
||||
# 直接以字符串形式处理
|
||||
if isinstance(nid, str):
|
||||
nid = nid.strip()
|
||||
|
||||
# 处理包含类名的格式,如 'StringNodeId(ns=4;s=...)' 或 'NumericNodeId(ns=2;i=...)'
|
||||
# 提取括号内的内容
|
||||
match_wrapped = re.match(r'(String|Numeric|Byte|Guid|TwoByteNode|FourByteNode)NodeId\((.*)\)', nid)
|
||||
if match_wrapped:
|
||||
# 提取括号内的实际 node_id 字符串
|
||||
nid = match_wrapped.group(2).strip()
|
||||
|
||||
# 常见短格式 'ns=2;i=1234' 或 'ns=2;s=SomeString'
|
||||
if re.match(r'^ns=\d+;[is]=', nid):
|
||||
self._node = self._client.get_node(nid)
|
||||
else:
|
||||
raise ValueError(f"无法解析节点ID: {self._node_id}")
|
||||
# 尝试提取 ns 和 i 或 s
|
||||
# 对于字符串标识符,可能包含特殊字符,使用非贪婪匹配
|
||||
m_num = re.search(r'ns=(\d+);i=(\d+)', nid)
|
||||
m_str = re.search(r'ns=(\d+);s=(.+?)(?:\)|$)', nid)
|
||||
if m_num:
|
||||
ns = int(m_num.group(1))
|
||||
identifier = int(m_num.group(2))
|
||||
node_id = NodeId(identifier, ns)
|
||||
self._node = self._client.get_node(node_id)
|
||||
elif m_str:
|
||||
ns = int(m_str.group(1))
|
||||
identifier = m_str.group(2).strip()
|
||||
# 对于字符串标识符,直接使用字符串格式
|
||||
node_id_str = f"ns={ns};s={identifier}"
|
||||
self._node = self._client.get_node(node_id_str)
|
||||
else:
|
||||
# 回退:尝试直接传入字符串(有些实现接受其它格式)
|
||||
try:
|
||||
self._node = self._client.get_node(self._node_id)
|
||||
except Exception as e:
|
||||
# 输出更详细的错误信息供调试
|
||||
print(f"获取节点失败(尝试直接字符串): {self._node_id}, 错误: {e}")
|
||||
raise
|
||||
else:
|
||||
# 直接使用节点ID字符串
|
||||
# 非字符串,尝试直接使用
|
||||
self._node = self._client.get_node(self._node_id)
|
||||
except Exception as e:
|
||||
print(f"获取节点失败: {self._node_id}, 错误: {e}")
|
||||
# 添加额外提示,帮助定位 BadNodeIdUnknown 问题
|
||||
print("提示: 请确认该 node_id 是否来自当前连接的服务器地址空间," \
|
||||
"以及 CSV/配置中名称与服务器 BrowseName 是否匹配。")
|
||||
raise
|
||||
return self._node
|
||||
|
||||
@@ -104,7 +149,56 @@ class Variable(Base):
|
||||
|
||||
def write(self, value: Any) -> bool:
|
||||
try:
|
||||
self._get_node().set_value(value)
|
||||
# 如果声明了数据类型,则尝试转换并使用对应的 Variant 写入
|
||||
coerced = value
|
||||
try:
|
||||
if self._data_type is not None:
|
||||
# 基于声明的数据类型做简单类型转换
|
||||
dt = self._data_type
|
||||
if dt in (DataType.SBYTE, DataType.BYTE, DataType.INT16, DataType.UINT16,
|
||||
DataType.INT32, DataType.UINT32, DataType.INT64, DataType.UINT64):
|
||||
# 数值类型 -> int
|
||||
if isinstance(value, str):
|
||||
coerced = int(value)
|
||||
else:
|
||||
coerced = int(value)
|
||||
elif dt in (DataType.FLOAT, DataType.DOUBLE):
|
||||
if isinstance(value, str):
|
||||
coerced = float(value)
|
||||
else:
|
||||
coerced = float(value)
|
||||
elif dt == DataType.BOOLEAN:
|
||||
if isinstance(value, str):
|
||||
v = value.strip().lower()
|
||||
if v in ("true", "1", "yes", "on"):
|
||||
coerced = True
|
||||
elif v in ("false", "0", "no", "off"):
|
||||
coerced = False
|
||||
else:
|
||||
coerced = bool(value)
|
||||
else:
|
||||
coerced = bool(value)
|
||||
elif dt == DataType.STRING or dt == DataType.BYTESTRING or dt == DataType.DATETIME:
|
||||
coerced = str(value)
|
||||
|
||||
# 使用 ua.Variant 明确指定 VariantType
|
||||
try:
|
||||
variant = ua.Variant(coerced, dt.value)
|
||||
self._get_node().set_value(variant)
|
||||
except Exception:
|
||||
# 回退:有些 set_value 实现接受 (value, variant_type)
|
||||
try:
|
||||
self._get_node().set_value(coerced, dt.value)
|
||||
except Exception:
|
||||
# 最后回退到直接写入(保持兼容性)
|
||||
self._get_node().set_value(coerced)
|
||||
else:
|
||||
# 未声明数据类型,直接写入
|
||||
self._get_node().set_value(value)
|
||||
except Exception:
|
||||
# 若在转换或按数据类型写入失败,尝试直接写入原始值并让上层捕获错误
|
||||
self._get_node().set_value(value)
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"写入变量 {self._name} 失败: {e}")
|
||||
@@ -120,20 +214,50 @@ class Method(Base):
|
||||
def _get_parent_node(self) -> Node:
|
||||
if self._parent_node is None:
|
||||
try:
|
||||
# 检查是否是NumericNodeId(ns=X;i=Y)格式
|
||||
if "NumericNodeId" in self._parent_node_id:
|
||||
# 从字符串中提取命名空间和标识符
|
||||
import re
|
||||
match = re.search(r'ns=(\d+);i=(\d+)', self._parent_node_id)
|
||||
if match:
|
||||
ns = int(match.group(1))
|
||||
identifier = int(match.group(2))
|
||||
node_id = NodeId(identifier, ns)
|
||||
self._parent_node = self._client.get_node(node_id)
|
||||
# 处理父节点ID,使用与_get_node相同的解析逻辑
|
||||
import re
|
||||
|
||||
nid = self._parent_node_id
|
||||
|
||||
# 如果已经是 NodeId 对象,直接使用
|
||||
try:
|
||||
from opcua.ua import NodeId as UaNodeId
|
||||
if isinstance(nid, UaNodeId):
|
||||
self._parent_node = self._client.get_node(nid)
|
||||
return self._parent_node
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 字符串处理
|
||||
if isinstance(nid, str):
|
||||
nid = nid.strip()
|
||||
|
||||
# 处理包含类名的格式
|
||||
match_wrapped = re.match(r'(String|Numeric|Byte|Guid|TwoByteNode|FourByteNode)NodeId\((.*)\)', nid)
|
||||
if match_wrapped:
|
||||
nid = match_wrapped.group(2).strip()
|
||||
|
||||
# 常见短格式
|
||||
if re.match(r'^ns=\d+;[is]=', nid):
|
||||
self._parent_node = self._client.get_node(nid)
|
||||
else:
|
||||
raise ValueError(f"无法解析父节点ID: {self._parent_node_id}")
|
||||
# 提取 ns 和 i 或 s
|
||||
m_num = re.search(r'ns=(\d+);i=(\d+)', nid)
|
||||
m_str = re.search(r'ns=(\d+);s=(.+?)(?:\)|$)', nid)
|
||||
if m_num:
|
||||
ns = int(m_num.group(1))
|
||||
identifier = int(m_num.group(2))
|
||||
node_id = NodeId(identifier, ns)
|
||||
self._parent_node = self._client.get_node(node_id)
|
||||
elif m_str:
|
||||
ns = int(m_str.group(1))
|
||||
identifier = m_str.group(2).strip()
|
||||
node_id_str = f"ns={ns};s={identifier}"
|
||||
self._parent_node = self._client.get_node(node_id_str)
|
||||
else:
|
||||
# 回退
|
||||
self._parent_node = self._client.get_node(self._parent_node_id)
|
||||
else:
|
||||
# 直接使用节点ID字符串
|
||||
self._parent_node = self._client.get_node(self._parent_node_id)
|
||||
except Exception as e:
|
||||
print(f"获取父节点失败: {self._parent_node_id}, 错误: {e}")
|
||||
|
||||
0
unilabos/devices/cameraSII/__init__.py
Normal file
0
unilabos/devices/cameraSII/__init__.py
Normal file
712
unilabos/devices/cameraSII/cameraDriver.py
Normal file
712
unilabos/devices/cameraSII/cameraDriver.py
Normal file
@@ -0,0 +1,712 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from typing import Optional, Dict, Any
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import websockets
|
||||
|
||||
logging.getLogger("zeep").setLevel(logging.WARNING)
|
||||
logging.getLogger("zeep.xsd.schema").setLevel(logging.WARNING)
|
||||
logging.getLogger("zeep.xsd.schema.schema").setLevel(logging.WARNING)
|
||||
from onvif import ONVIFCamera # 新增:ONVIF PTZ 控制
|
||||
|
||||
|
||||
# ======================= 独立的 PTZController =======================
|
||||
class PTZController:
|
||||
def __init__(self, host: str, port: int, user: str, password: str):
|
||||
"""
|
||||
:param host: 摄像机 IP 或域名(和 RTSP 的一样即可)
|
||||
:param port: ONVIF 端口(多数为 80,看你的设备)
|
||||
:param user: 摄像机用户名
|
||||
:param password: 摄像机密码
|
||||
"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
self.cam: Optional[ONVIFCamera] = None
|
||||
self.media_service = None
|
||||
self.ptz_service = None
|
||||
self.profile = None
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""
|
||||
建立 ONVIF 连接并初始化 PTZ 能力,失败返回 False(不抛异常)
|
||||
Note: 首先 pip install onvif-zeep
|
||||
"""
|
||||
try:
|
||||
self.cam = ONVIFCamera(self.host, self.port, self.user, self.password)
|
||||
self.media_service = self.cam.create_media_service()
|
||||
self.ptz_service = self.cam.create_ptz_service()
|
||||
profiles = self.media_service.GetProfiles()
|
||||
if not profiles:
|
||||
print("[PTZ] No media profiles found on camera.", file=sys.stderr)
|
||||
return False
|
||||
self.profile = profiles[0]
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[PTZ] Failed to init ONVIF PTZ: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def _continuous_move(self, pan: float, tilt: float, zoom: float, duration: float) -> bool:
|
||||
"""
|
||||
连续移动一段时间(秒),之后自动停止。
|
||||
此函数为阻塞模式:只有在 Stop 调用结束后,才返回 True/False。
|
||||
"""
|
||||
if not self.ptz_service or not self.profile:
|
||||
print("[PTZ] _continuous_move: ptz_service or profile not ready", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# 进入前先强行停一下,避免前一次残留动作
|
||||
self._force_stop()
|
||||
|
||||
req = self.ptz_service.create_type("ContinuousMove")
|
||||
req.ProfileToken = self.profile.token
|
||||
|
||||
req.Velocity = {
|
||||
"PanTilt": {"x": pan, "y": tilt},
|
||||
"Zoom": {"x": zoom},
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[PTZ] ContinuousMove start: pan={pan}, tilt={tilt}, zoom={zoom}, duration={duration}", file=sys.stderr)
|
||||
self.ptz_service.ContinuousMove(req)
|
||||
except Exception as e:
|
||||
print(f"[PTZ] ContinuousMove failed: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# 阻塞等待:这里决定“运动时间”
|
||||
import time
|
||||
wait_seconds = max(2 * duration, 0.0)
|
||||
time.sleep(wait_seconds)
|
||||
|
||||
# 运动完成后强制停止
|
||||
return self._force_stop()
|
||||
|
||||
def stop(self) -> bool:
|
||||
"""
|
||||
阻塞调用 Stop(带重试),成功 True,失败 False。
|
||||
"""
|
||||
return self._force_stop()
|
||||
|
||||
# ------- 对外动作接口(给 CameraController 调用) -------
|
||||
# 所有接口都为“阻塞模式”:只有在运动 + Stop 完成后才返回 True/False
|
||||
|
||||
def move_up(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
||||
print(f"[PTZ] move_up called, speed={speed}, duration={duration}", file=sys.stderr)
|
||||
return self._continuous_move(pan=0.0, tilt=+speed, zoom=0.0, duration=duration)
|
||||
|
||||
def move_down(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
||||
print(f"[PTZ] move_down called, speed={speed}, duration={duration}", file=sys.stderr)
|
||||
return self._continuous_move(pan=0.0, tilt=-speed, zoom=0.0, duration=duration)
|
||||
|
||||
def move_left(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
print(f"[PTZ] move_left called, speed={speed}, duration={duration}", file=sys.stderr)
|
||||
return self._continuous_move(pan=-speed, tilt=0.0, zoom=0.0, duration=duration)
|
||||
|
||||
def move_right(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
print(f"[PTZ] move_right called, speed={speed}, duration={duration}", file=sys.stderr)
|
||||
return self._continuous_move(pan=+speed, tilt=0.0, zoom=0.0, duration=duration)
|
||||
|
||||
# ------- 占位的变倍接口(当前设备不支持) -------
|
||||
def zoom_in(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
"""
|
||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
||||
"""
|
||||
print("[PTZ] zoom_in is disabled for this device.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def zoom_out(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
"""
|
||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
||||
"""
|
||||
print("[PTZ] zoom_out is disabled for this device.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def _force_stop(self, retries: int = 3, delay: float = 0.1) -> bool:
|
||||
"""
|
||||
尝试多次调用 Stop,作为“强制停止”手段。
|
||||
:param retries: 重试次数
|
||||
:param delay: 每次重试间隔(秒)
|
||||
"""
|
||||
if not self.ptz_service or not self.profile:
|
||||
print("[PTZ] _force_stop: ptz_service or profile not ready", file=sys.stderr)
|
||||
return False
|
||||
|
||||
import time
|
||||
last_error = None
|
||||
for i in range(retries):
|
||||
try:
|
||||
print(f"[PTZ] _force_stop: calling Stop(), attempt={i+1}", file=sys.stderr)
|
||||
self.ptz_service.Stop({"ProfileToken": self.profile.token})
|
||||
print("[PTZ] _force_stop: Stop() returned OK", file=sys.stderr)
|
||||
return True
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
print(f"[PTZ] _force_stop: Stop() failed at attempt {i+1}: {e}", file=sys.stderr)
|
||||
time.sleep(delay)
|
||||
|
||||
print(f"[PTZ] _force_stop: all {retries} attempts failed, last error: {last_error}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# ======================= CameraController(加入 PTZ) =======================
|
||||
|
||||
class CameraController:
|
||||
"""
|
||||
Uni-Lab-OS 摄像头驱动(driver 形式)
|
||||
启动 Uni-Lab-OS 后,立即开始推流
|
||||
|
||||
- WebSocket 信令:通过 signal_backend_url 连接到后端
|
||||
例如: wss://sciol.ac.cn/api/realtime/signal/host/<host_id>
|
||||
- 媒体服务器:通过 rtmp_url / webrtc_api / webrtc_stream_url
|
||||
当前配置为 SRS,与独立 HostSimulator 独立运行脚本保持一致。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host_id: str = "demo-host",
|
||||
|
||||
# (1)信令后端(WebSocket)
|
||||
signal_backend_url: str = "wss://sciol.ac.cn/api/realtime/signal/host",
|
||||
|
||||
# (2)媒体后端(RTMP + WebRTC API)
|
||||
rtmp_url: str = "rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
||||
webrtc_api: str = "https://srs.sciol.ac.cn/rtc/v1/play/",
|
||||
webrtc_stream_url: str = "webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
||||
camera_rtsp_url: str = "",
|
||||
|
||||
# (3)PTZ 控制相关(ONVIF)
|
||||
ptz_host: str = "", # 一般就是摄像头 IP,比如 "192.168.31.164"
|
||||
ptz_port: int = 80, # ONVIF 端口,不一定是 80,按实际情况改
|
||||
ptz_user: str = "", # admin
|
||||
ptz_password: str = "", # admin123
|
||||
):
|
||||
self.host_id = host_id
|
||||
self.camera_rtsp_url = camera_rtsp_url
|
||||
|
||||
# 拼接最终的 WebSocket URL:.../host/<host_id>
|
||||
signal_backend_url = signal_backend_url.rstrip("/")
|
||||
if not signal_backend_url.endswith("/host"):
|
||||
signal_backend_url = signal_backend_url + "/host"
|
||||
self.signal_backend_url = f"{signal_backend_url}/{host_id}"
|
||||
|
||||
# 媒体服务器配置
|
||||
self.rtmp_url = rtmp_url
|
||||
self.webrtc_api = webrtc_api
|
||||
self.webrtc_stream_url = webrtc_stream_url
|
||||
|
||||
# PTZ 控制
|
||||
self.ptz_host = ptz_host
|
||||
self.ptz_port = ptz_port
|
||||
self.ptz_user = ptz_user
|
||||
self.ptz_password = ptz_password
|
||||
self._ptz: Optional[PTZController] = None
|
||||
self._init_ptz_if_possible()
|
||||
|
||||
# 运行时状态
|
||||
self._ws: Optional[object] = None
|
||||
self._ffmpeg_process: Optional[subprocess.Popen] = None
|
||||
self._running = False
|
||||
self._loop_task: Optional[asyncio.Future] = None
|
||||
|
||||
# 事件循环 & 线程
|
||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
self._loop_thread: Optional[threading.Thread] = None
|
||||
|
||||
try:
|
||||
self.start()
|
||||
except Exception as e:
|
||||
print(f"[CameraController] __init__ auto start failed: {e}", file=sys.stderr)
|
||||
|
||||
# ------------------------ PTZ 初始化 ------------------------
|
||||
|
||||
# ------------------------ PTZ 公开动作方法(一个动作一个函数) ------------------------
|
||||
|
||||
def ptz_move_up(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
||||
print(f"[CameraController] ptz_move_up called, speed={speed}, duration={duration}")
|
||||
return self._ptz.move_up(speed=speed, duration=duration)
|
||||
|
||||
def ptz_move_down(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
||||
print(f"[CameraController] ptz_move_down called, speed={speed}, duration={duration}")
|
||||
return self._ptz.move_down(speed=speed, duration=duration)
|
||||
|
||||
def ptz_move_left(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
print(f"[CameraController] ptz_move_left called, speed={speed}, duration={duration}")
|
||||
return self._ptz.move_left(speed=speed, duration=duration)
|
||||
|
||||
def ptz_move_right(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
print(f"[CameraController] ptz_move_right called, speed={speed}, duration={duration}")
|
||||
return self._ptz.move_right(speed=speed, duration=duration)
|
||||
|
||||
def zoom_in(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
"""
|
||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
||||
"""
|
||||
print("[PTZ] zoom_in is disabled for this device.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def zoom_out(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
||||
"""
|
||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
||||
"""
|
||||
print("[PTZ] zoom_out is disabled for this device.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def ptz_stop(self):
|
||||
if self._ptz is None:
|
||||
print("[CameraController] PTZ not initialized.", file=sys.stderr)
|
||||
return
|
||||
self._ptz.stop()
|
||||
|
||||
def _init_ptz_if_possible(self):
|
||||
"""
|
||||
根据 ptz_host / user / password 初始化 PTZ;
|
||||
如果配置信息不全则不启用 PTZ(静默)。
|
||||
"""
|
||||
if not (self.ptz_host and self.ptz_user and self.ptz_password):
|
||||
return
|
||||
ctrl = PTZController(
|
||||
host=self.ptz_host,
|
||||
port=self.ptz_port,
|
||||
user=self.ptz_user,
|
||||
password=self.ptz_password,
|
||||
)
|
||||
if ctrl.connect():
|
||||
self._ptz = ctrl
|
||||
else:
|
||||
self._ptz = None
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 对外暴露的方法:供 Uni-Lab-OS 调用
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def start(self, config: Optional[Dict[str, Any]] = None):
|
||||
"""
|
||||
启动 Camera 连接 & 消息循环,并在启动时就开启 FFmpeg 推流,
|
||||
"""
|
||||
|
||||
if self._running:
|
||||
return {"status": "already_running", "host_id": self.host_id}
|
||||
|
||||
# 应用 config 覆盖(如果有)
|
||||
if config:
|
||||
self.camera_rtsp_url = config.get("camera_rtsp_url", self.camera_rtsp_url)
|
||||
cfg_host_id = config.get("host_id")
|
||||
if cfg_host_id:
|
||||
self.host_id = cfg_host_id
|
||||
|
||||
signal_backend_url = config.get("signal_backend_url")
|
||||
if signal_backend_url:
|
||||
signal_backend_url = signal_backend_url.rstrip("/")
|
||||
if not signal_backend_url.endswith("/host"):
|
||||
signal_backend_url = signal_backend_url + "/host"
|
||||
self.signal_backend_url = f"{signal_backend_url}/{self.host_id}"
|
||||
|
||||
self.rtmp_url = config.get("rtmp_url", self.rtmp_url)
|
||||
self.webrtc_api = config.get("webrtc_api", self.webrtc_api)
|
||||
self.webrtc_stream_url = config.get(
|
||||
"webrtc_stream_url", self.webrtc_stream_url
|
||||
)
|
||||
|
||||
# PTZ 相关配置也允许通过 config 注入
|
||||
self.ptz_host = config.get("ptz_host", self.ptz_host)
|
||||
self.ptz_port = int(config.get("ptz_port", self.ptz_port))
|
||||
self.ptz_user = config.get("ptz_user", self.ptz_user)
|
||||
self.ptz_password = config.get("ptz_password", self.ptz_password)
|
||||
self._init_ptz_if_possible()
|
||||
|
||||
self._running = True
|
||||
|
||||
# === start 时启动 FFmpeg 推流 ===
|
||||
self._start_ffmpeg()
|
||||
|
||||
# 创建新的事件循环和线程(用于 WebSocket 信令)
|
||||
self._loop = asyncio.new_event_loop()
|
||||
|
||||
def loop_runner(loop: asyncio.AbstractEventLoop):
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_forever()
|
||||
except Exception as e:
|
||||
print(f"[CameraController] event loop error: {e}", file=sys.stderr)
|
||||
|
||||
self._loop_thread = threading.Thread(
|
||||
target=loop_runner, args=(self._loop,), daemon=True
|
||||
)
|
||||
self._loop_thread.start()
|
||||
|
||||
self._loop_task = asyncio.run_coroutine_threadsafe(
|
||||
self._run_main_loop(), self._loop
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "started",
|
||||
"host_id": self.host_id,
|
||||
"signal_backend_url": self.signal_backend_url,
|
||||
"rtmp_url": self.rtmp_url,
|
||||
"webrtc_api": self.webrtc_api,
|
||||
"webrtc_stream_url": self.webrtc_stream_url,
|
||||
}
|
||||
|
||||
def stop(self) -> Dict[str, Any]:
|
||||
"""
|
||||
停止推流 & 断开 WebSocket,并关闭事件循环线程。
|
||||
"""
|
||||
self._running = False
|
||||
|
||||
self._stop_ffmpeg()
|
||||
|
||||
if self._ws and self._loop is not None:
|
||||
async def close_ws():
|
||||
try:
|
||||
await self._ws.close()
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when closing WebSocket: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
asyncio.run_coroutine_threadsafe(close_ws(), self._loop)
|
||||
|
||||
if self._loop_task is not None:
|
||||
if not self._loop_task.done():
|
||||
self._loop_task.cancel()
|
||||
try:
|
||||
self._loop_task.result()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] main loop task error in stop(): {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
finally:
|
||||
self._loop_task = None
|
||||
|
||||
if self._loop is not None:
|
||||
try:
|
||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when stopping event loop: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
if self._loop_thread is not None:
|
||||
try:
|
||||
self._loop_thread.join(timeout=5)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when joining loop thread: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
finally:
|
||||
self._loop_thread = None
|
||||
|
||||
self._ws = None
|
||||
self._loop = None
|
||||
|
||||
return {"status": "stopped", "host_id": self.host_id}
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""
|
||||
查询当前状态,方便在 Uni-Lab-OS 中做监控。
|
||||
"""
|
||||
ws_closed = None
|
||||
if self._ws is not None:
|
||||
ws_closed = getattr(self._ws, "closed", None)
|
||||
|
||||
if ws_closed is None:
|
||||
websocket_connected = self._ws is not None
|
||||
else:
|
||||
websocket_connected = (self._ws is not None) and (not ws_closed)
|
||||
|
||||
return {
|
||||
"host_id": self.host_id,
|
||||
"running": self._running,
|
||||
"websocket_connected": websocket_connected,
|
||||
"ffmpeg_running": bool(
|
||||
self._ffmpeg_process and self._ffmpeg_process.poll() is None
|
||||
),
|
||||
"signal_backend_url": self.signal_backend_url,
|
||||
"rtmp_url": self.rtmp_url,
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 内部实现逻辑:WebSocket 循环 / FFmpeg / WebRTC Offer 处理
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
async def _run_main_loop(self):
|
||||
try:
|
||||
while self._running:
|
||||
try:
|
||||
async with websockets.connect(self.signal_backend_url) as ws:
|
||||
self._ws = ws
|
||||
await self._recv_loop()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
if self._running:
|
||||
print(
|
||||
f"[CameraController] WebSocket connection error: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def _recv_loop(self):
|
||||
assert self._ws is not None
|
||||
ws = self._ws
|
||||
|
||||
async for message in ws:
|
||||
try:
|
||||
data = json.loads(message)
|
||||
except json.JSONDecodeError:
|
||||
print(
|
||||
f"[CameraController] received non-JSON message: {message}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
await self._handle_message(data)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error while handling message {data}: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
async def _handle_message(self, data: Dict[str, Any]):
|
||||
"""
|
||||
处理来自信令后端的消息:
|
||||
- command: start_stream / stop_stream / ptz_xxx
|
||||
- type: offer (WebRTC)
|
||||
"""
|
||||
cmd = data.get("command")
|
||||
|
||||
# ---------- 推流控制 ----------
|
||||
if cmd == "start_stream":
|
||||
try:
|
||||
self._start_ffmpeg()
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when starting FFmpeg on start_stream: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
|
||||
if cmd == "stop_stream":
|
||||
try:
|
||||
self._stop_ffmpeg()
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when stopping FFmpeg on stop_stream: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
|
||||
# # ---------- PTZ 控制 ----------
|
||||
# # 例如信令可以发:
|
||||
# # {"command": "ptz_move", "direction": "down", "speed": 0.5, "duration": 0.5}
|
||||
# if cmd == "ptz_move":
|
||||
# if self._ptz is None:
|
||||
# # 没有初始化 PTZ,静默忽略或打印一条
|
||||
# print("[CameraController] PTZ not initialized.", file=sys.stderr)
|
||||
# return
|
||||
|
||||
# direction = data.get("direction", "")
|
||||
# speed = float(data.get("speed", 0.5))
|
||||
# duration = float(data.get("duration", 0.5))
|
||||
|
||||
# try:
|
||||
# if direction == "up":
|
||||
# self._ptz.move_up(speed=speed, duration=duration)
|
||||
# elif direction == "down":
|
||||
# self._ptz.move_down(speed=speed, duration=duration)
|
||||
# elif direction == "left":
|
||||
# self._ptz.move_left(speed=speed, duration=duration)
|
||||
# elif direction == "right":
|
||||
# self._ptz.move_right(speed=speed, duration=duration)
|
||||
# elif direction == "zoom_in":
|
||||
# self._ptz.zoom_in(speed=speed, duration=duration)
|
||||
# elif direction == "zoom_out":
|
||||
# self._ptz.zoom_out(speed=speed, duration=duration)
|
||||
# elif direction == "stop":
|
||||
# self._ptz.stop()
|
||||
# else:
|
||||
# # 未知方向,忽略
|
||||
# pass
|
||||
# except Exception as e:
|
||||
# print(
|
||||
# f"[CameraController] error when handling PTZ move: {e}",
|
||||
# file=sys.stderr,
|
||||
# )
|
||||
# return
|
||||
|
||||
# ---------- WebRTC Offer ----------
|
||||
if data.get("type") == "offer":
|
||||
offer_sdp = data.get("sdp", "")
|
||||
camera_id = data.get("cameraId", "camera-01")
|
||||
try:
|
||||
answer_sdp = await self._handle_webrtc_offer(offer_sdp)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when handling WebRTC offer: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
|
||||
if self._ws:
|
||||
answer_payload = {
|
||||
"type": "answer",
|
||||
"sdp": answer_sdp,
|
||||
"cameraId": camera_id,
|
||||
"hostId": self.host_id,
|
||||
}
|
||||
try:
|
||||
await self._ws.send(json.dumps(answer_payload))
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when sending WebRTC answer: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# ------------------------ FFmpeg 相关 ------------------------
|
||||
|
||||
def _start_ffmpeg(self):
|
||||
if self._ffmpeg_process and self._ffmpeg_process.poll() is None:
|
||||
return
|
||||
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-rtsp_transport", "tcp",
|
||||
"-i", self.camera_rtsp_url,
|
||||
|
||||
"-c:v", "libx264",
|
||||
"-preset", "ultrafast",
|
||||
"-tune", "zerolatency",
|
||||
"-profile:v", "baseline",
|
||||
"-b:v", "1M",
|
||||
"-maxrate", "1M",
|
||||
"-bufsize", "2M",
|
||||
"-g", "10",
|
||||
"-keyint_min", "10",
|
||||
"-sc_threshold", "0",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-x264-params", "bframes=0",
|
||||
|
||||
"-c:a", "aac",
|
||||
"-ar", "44100",
|
||||
"-ac", "1",
|
||||
"-b:a", "64k",
|
||||
|
||||
"-f", "flv",
|
||||
self.rtmp_url,
|
||||
]
|
||||
|
||||
try:
|
||||
self._ffmpeg_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
shell=False,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[CameraController] failed to start FFmpeg: {e}", file=sys.stderr)
|
||||
self._ffmpeg_process = None
|
||||
raise
|
||||
|
||||
def _stop_ffmpeg(self):
|
||||
proc = self._ffmpeg_process
|
||||
|
||||
if proc and proc.poll() is None:
|
||||
try:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
proc.kill()
|
||||
try:
|
||||
proc.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
print(
|
||||
f"[CameraController] FFmpeg process did not exit even after kill (pid={proc.pid})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] failed to kill FFmpeg process: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] error when stopping FFmpeg: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
self._ffmpeg_process = None
|
||||
|
||||
# ------------------------ WebRTC Offer 相关 ------------------------
|
||||
|
||||
async def _handle_webrtc_offer(self, offer_sdp: str) -> str:
|
||||
payload = {
|
||||
"api": self.webrtc_api,
|
||||
"streamurl": self.webrtc_stream_url,
|
||||
"sdp": offer_sdp,
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
def _do_request():
|
||||
return requests.post(
|
||||
self.webrtc_api,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
resp = await loop.run_in_executor(None, _do_request)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] failed to send offer to media server: {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] media server HTTP error: {e}, "
|
||||
f"status={resp.status_code}, body={resp.text[:200]}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[CameraController] failed to parse media server JSON: {e}, "
|
||||
f"raw={resp.text[:200]}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
answer_sdp = data.get("sdp", "")
|
||||
if not answer_sdp:
|
||||
msg = f"empty SDP from media server: {data}"
|
||||
print(f"[CameraController] {msg}", file=sys.stderr)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
return answer_sdp
|
||||
401
unilabos/devices/cameraSII/cameraUSB.py
Normal file
401
unilabos/devices/cameraSII/cameraUSB.py
Normal file
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
import requests
|
||||
import websockets
|
||||
|
||||
|
||||
class CameraController:
|
||||
"""
|
||||
Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
||||
|
||||
- WebSocket 信令:signal_backend_url 连接到后端
|
||||
例如: wss://sciol.ac.cn/api/realtime/signal/host/<host_id>
|
||||
- 媒体服务器:RTMP 推流到 rtmp_url;WebRTC offer 转发到 SRS 的 webrtc_api
|
||||
- 视频源:本地 USB 摄像头(V4L2,默认 /dev/video0)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host_id: str = "demo-host",
|
||||
signal_backend_url: str = "wss://sciol.ac.cn/api/realtime/signal/host",
|
||||
rtmp_url: str = "rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
||||
webrtc_api: str = "https://srs.sciol.ac.cn/rtc/v1/play/",
|
||||
webrtc_stream_url: str = "webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
||||
video_device: str = "/dev/video0",
|
||||
width: int = 1280,
|
||||
height: int = 720,
|
||||
fps: int = 30,
|
||||
video_bitrate: str = "1500k",
|
||||
audio_device: Optional[str] = None, # 比如 "hw:1,0",没有音频就保持 None
|
||||
audio_bitrate: str = "64k",
|
||||
):
|
||||
self.host_id = host_id
|
||||
|
||||
# 拼接最终 WebSocket URL:.../host/<host_id>
|
||||
signal_backend_url = signal_backend_url.rstrip("/")
|
||||
if not signal_backend_url.endswith("/host"):
|
||||
signal_backend_url = signal_backend_url + "/host"
|
||||
self.signal_backend_url = f"{signal_backend_url}/{host_id}"
|
||||
|
||||
# 媒体服务器配置
|
||||
self.rtmp_url = rtmp_url
|
||||
self.webrtc_api = webrtc_api
|
||||
self.webrtc_stream_url = webrtc_stream_url
|
||||
|
||||
# 本地采集配置
|
||||
self.video_device = video_device
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
self.fps = int(fps)
|
||||
self.video_bitrate = video_bitrate
|
||||
self.audio_device = audio_device
|
||||
self.audio_bitrate = audio_bitrate
|
||||
|
||||
# 运行时状态
|
||||
self._ws: Optional[object] = None
|
||||
self._ffmpeg_process: Optional[subprocess.Popen] = None
|
||||
self._running = False
|
||||
self._loop_task: Optional[asyncio.Future] = None
|
||||
|
||||
# 事件循环 & 线程
|
||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
self._loop_thread: Optional[threading.Thread] = None
|
||||
|
||||
try:
|
||||
self.start()
|
||||
except Exception as e:
|
||||
print(f"[CameraController] __init__ auto start failed: {e}", file=sys.stderr)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 对外方法
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def start(self, config: Optional[Dict[str, Any]] = None):
|
||||
if self._running:
|
||||
return {"status": "already_running", "host_id": self.host_id}
|
||||
|
||||
# 应用 config 覆盖(如果有)
|
||||
if config:
|
||||
cfg_host_id = config.get("host_id")
|
||||
if cfg_host_id:
|
||||
self.host_id = cfg_host_id
|
||||
|
||||
signal_backend_url = config.get("signal_backend_url")
|
||||
if signal_backend_url:
|
||||
signal_backend_url = signal_backend_url.rstrip("/")
|
||||
if not signal_backend_url.endswith("/host"):
|
||||
signal_backend_url = signal_backend_url + "/host"
|
||||
self.signal_backend_url = f"{signal_backend_url}/{self.host_id}"
|
||||
|
||||
self.rtmp_url = config.get("rtmp_url", self.rtmp_url)
|
||||
self.webrtc_api = config.get("webrtc_api", self.webrtc_api)
|
||||
self.webrtc_stream_url = config.get("webrtc_stream_url", self.webrtc_stream_url)
|
||||
|
||||
self.video_device = config.get("video_device", self.video_device)
|
||||
self.width = int(config.get("width", self.width))
|
||||
self.height = int(config.get("height", self.height))
|
||||
self.fps = int(config.get("fps", self.fps))
|
||||
self.video_bitrate = config.get("video_bitrate", self.video_bitrate)
|
||||
self.audio_device = config.get("audio_device", self.audio_device)
|
||||
self.audio_bitrate = config.get("audio_bitrate", self.audio_bitrate)
|
||||
|
||||
self._running = True
|
||||
|
||||
print("[CameraController] start(): starting FFmpeg streaming...", file=sys.stderr)
|
||||
self._start_ffmpeg()
|
||||
|
||||
self._loop = asyncio.new_event_loop()
|
||||
|
||||
def loop_runner(loop: asyncio.AbstractEventLoop):
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_forever()
|
||||
except Exception as e:
|
||||
print(f"[CameraController] event loop error: {e}", file=sys.stderr)
|
||||
|
||||
self._loop_thread = threading.Thread(target=loop_runner, args=(self._loop,), daemon=True)
|
||||
self._loop_thread.start()
|
||||
|
||||
self._loop_task = asyncio.run_coroutine_threadsafe(self._run_main_loop(), self._loop)
|
||||
|
||||
return {
|
||||
"status": "started",
|
||||
"host_id": self.host_id,
|
||||
"signal_backend_url": self.signal_backend_url,
|
||||
"rtmp_url": self.rtmp_url,
|
||||
"webrtc_api": self.webrtc_api,
|
||||
"webrtc_stream_url": self.webrtc_stream_url,
|
||||
"video_device": self.video_device,
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"fps": self.fps,
|
||||
"video_bitrate": self.video_bitrate,
|
||||
"audio_device": self.audio_device,
|
||||
}
|
||||
|
||||
def stop(self) -> Dict[str, Any]:
|
||||
self._running = False
|
||||
|
||||
# 先取消主任务(让 ws connect/sleep 尽快退出)
|
||||
if self._loop_task is not None and not self._loop_task.done():
|
||||
self._loop_task.cancel()
|
||||
|
||||
# 停止推流
|
||||
self._stop_ffmpeg()
|
||||
|
||||
# 关闭 WebSocket(在 loop 中执行)
|
||||
if self._ws and self._loop is not None:
|
||||
|
||||
async def close_ws():
|
||||
try:
|
||||
await self._ws.close()
|
||||
except Exception as e:
|
||||
print(f"[CameraController] error closing WebSocket: {e}", file=sys.stderr)
|
||||
|
||||
try:
|
||||
asyncio.run_coroutine_threadsafe(close_ws(), self._loop)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 停止事件循环
|
||||
if self._loop is not None:
|
||||
try:
|
||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
||||
except Exception as e:
|
||||
print(f"[CameraController] error stopping loop: {e}", file=sys.stderr)
|
||||
|
||||
# 等待线程退出
|
||||
if self._loop_thread is not None:
|
||||
try:
|
||||
self._loop_thread.join(timeout=5)
|
||||
except Exception as e:
|
||||
print(f"[CameraController] error joining loop thread: {e}", file=sys.stderr)
|
||||
|
||||
self._ws = None
|
||||
self._loop_task = None
|
||||
self._loop = None
|
||||
self._loop_thread = None
|
||||
|
||||
return {"status": "stopped", "host_id": self.host_id}
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
ws_closed = None
|
||||
if self._ws is not None:
|
||||
ws_closed = getattr(self._ws, "closed", None)
|
||||
|
||||
if ws_closed is None:
|
||||
websocket_connected = self._ws is not None
|
||||
else:
|
||||
websocket_connected = (self._ws is not None) and (not ws_closed)
|
||||
|
||||
return {
|
||||
"host_id": self.host_id,
|
||||
"running": self._running,
|
||||
"websocket_connected": websocket_connected,
|
||||
"ffmpeg_running": bool(self._ffmpeg_process and self._ffmpeg_process.poll() is None),
|
||||
"signal_backend_url": self.signal_backend_url,
|
||||
"rtmp_url": self.rtmp_url,
|
||||
"video_device": self.video_device,
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"fps": self.fps,
|
||||
"video_bitrate": self.video_bitrate,
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# WebSocket / 信令
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
async def _run_main_loop(self):
|
||||
print("[CameraController] main loop started", file=sys.stderr)
|
||||
try:
|
||||
while self._running:
|
||||
try:
|
||||
async with websockets.connect(self.signal_backend_url) as ws:
|
||||
self._ws = ws
|
||||
print(f"[CameraController] WebSocket connected: {self.signal_backend_url}", file=sys.stderr)
|
||||
await self._recv_loop()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
if self._running:
|
||||
print(f"[CameraController] WebSocket connection error: {e}", file=sys.stderr)
|
||||
await asyncio.sleep(3)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
print("[CameraController] main loop exited", file=sys.stderr)
|
||||
|
||||
async def _recv_loop(self):
|
||||
assert self._ws is not None
|
||||
ws = self._ws
|
||||
|
||||
async for message in ws:
|
||||
try:
|
||||
data = json.loads(message)
|
||||
except json.JSONDecodeError:
|
||||
print(f"[CameraController] non-JSON message: {message}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
try:
|
||||
await self._handle_message(data)
|
||||
except Exception as e:
|
||||
print(f"[CameraController] error handling message {data}: {e}", file=sys.stderr)
|
||||
|
||||
async def _handle_message(self, data: Dict[str, Any]):
|
||||
cmd = data.get("command")
|
||||
|
||||
if cmd == "start_stream":
|
||||
self._start_ffmpeg()
|
||||
return
|
||||
|
||||
if cmd == "stop_stream":
|
||||
self._stop_ffmpeg()
|
||||
return
|
||||
|
||||
if data.get("type") == "offer":
|
||||
offer_sdp = data.get("sdp", "")
|
||||
camera_id = data.get("cameraId", "camera-01")
|
||||
|
||||
answer_sdp = await self._handle_webrtc_offer(offer_sdp)
|
||||
|
||||
if self._ws:
|
||||
answer_payload = {
|
||||
"type": "answer",
|
||||
"sdp": answer_sdp,
|
||||
"cameraId": camera_id,
|
||||
"hostId": self.host_id,
|
||||
}
|
||||
await self._ws.send(json.dumps(answer_payload))
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# FFmpeg 推流(V4L2 USB 摄像头)
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def _start_ffmpeg(self):
|
||||
if self._ffmpeg_process and self._ffmpeg_process.poll() is None:
|
||||
return
|
||||
|
||||
# 兼容性优先:不强制输入像素格式;失败再通过外部调整 width/height/fps
|
||||
video_size = f"{self.width}x{self.height}"
|
||||
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
|
||||
# video input
|
||||
"-f", "v4l2",
|
||||
"-framerate", str(self.fps),
|
||||
"-video_size", video_size,
|
||||
"-i", self.video_device,
|
||||
]
|
||||
|
||||
# optional audio input
|
||||
if self.audio_device:
|
||||
cmd += [
|
||||
"-f", "alsa",
|
||||
"-i", self.audio_device,
|
||||
"-c:a", "aac",
|
||||
"-b:a", self.audio_bitrate,
|
||||
"-ar", "44100",
|
||||
"-ac", "1",
|
||||
]
|
||||
else:
|
||||
cmd += ["-an"]
|
||||
|
||||
# video encode + rtmp out
|
||||
cmd += [
|
||||
"-c:v", "libx264",
|
||||
"-preset", "ultrafast",
|
||||
"-tune", "zerolatency",
|
||||
"-profile:v", "baseline",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-b:v", self.video_bitrate,
|
||||
"-maxrate", self.video_bitrate,
|
||||
"-bufsize", "2M",
|
||||
"-g", str(max(self.fps, 10)),
|
||||
"-keyint_min", str(max(self.fps, 10)),
|
||||
"-sc_threshold", "0",
|
||||
"-x264-params", "bframes=0",
|
||||
|
||||
"-f", "flv",
|
||||
self.rtmp_url,
|
||||
]
|
||||
|
||||
print(f"[CameraController] starting FFmpeg: {' '.join(cmd)}", file=sys.stderr)
|
||||
|
||||
try:
|
||||
# 不再丢弃日志,至少能看到 ffmpeg 报错(调试很关键)
|
||||
self._ffmpeg_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=sys.stderr,
|
||||
shell=False,
|
||||
)
|
||||
except Exception as e:
|
||||
self._ffmpeg_process = None
|
||||
print(f"[CameraController] failed to start FFmpeg: {e}", file=sys.stderr)
|
||||
|
||||
def _stop_ffmpeg(self):
|
||||
proc = self._ffmpeg_process
|
||||
if proc and proc.poll() is None:
|
||||
try:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
except Exception as e:
|
||||
print(f"[CameraController] error stopping FFmpeg: {e}", file=sys.stderr)
|
||||
self._ffmpeg_process = None
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# WebRTC offer -> SRS
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
async def _handle_webrtc_offer(self, offer_sdp: str) -> str:
|
||||
payload = {
|
||||
"api": self.webrtc_api,
|
||||
"streamurl": self.webrtc_stream_url,
|
||||
"sdp": offer_sdp,
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
def _do_post():
|
||||
return requests.post(self.webrtc_api, json=payload, headers=headers, timeout=10)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
resp = await loop.run_in_executor(None, _do_post)
|
||||
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
answer_sdp = data.get("sdp", "")
|
||||
if not answer_sdp:
|
||||
raise RuntimeError(f"empty SDP from media server: {data}")
|
||||
return answer_sdp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 直接运行用于手动测试
|
||||
c = CameraController(
|
||||
host_id="demo-host",
|
||||
video_device="/dev/video0",
|
||||
width=1280,
|
||||
height=720,
|
||||
fps=30,
|
||||
video_bitrate="1500k",
|
||||
audio_device=None,
|
||||
)
|
||||
try:
|
||||
while True:
|
||||
asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
c.stop()
|
||||
51
unilabos/devices/cameraSII/cameraUSB_test.py
Normal file
51
unilabos/devices/cameraSII/cameraUSB_test.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import json
|
||||
|
||||
from cameraUSB import CameraController
|
||||
|
||||
|
||||
def main():
|
||||
# 按你的实际情况改
|
||||
cfg = dict(
|
||||
host_id="demo-host",
|
||||
signal_backend_url="wss://sciol.ac.cn/api/realtime/signal/host",
|
||||
rtmp_url="rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
||||
webrtc_api="https://srs.sciol.ac.cn/rtc/v1/play/",
|
||||
webrtc_stream_url="webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
||||
video_device="/dev/video7",
|
||||
width=1280,
|
||||
height=720,
|
||||
fps=30,
|
||||
video_bitrate="1500k",
|
||||
audio_device=None,
|
||||
)
|
||||
|
||||
c = CameraController(**cfg)
|
||||
|
||||
# 可选:如果你不想依赖 __init__ 自动 start,可以这样显式调用:
|
||||
# c = CameraController(host_id=cfg["host_id"])
|
||||
# c.start(cfg)
|
||||
|
||||
run_seconds = 30 # 测试运行时长
|
||||
t0 = time.time()
|
||||
|
||||
try:
|
||||
while True:
|
||||
st = c.get_status()
|
||||
print(json.dumps(st, ensure_ascii=False, indent=2))
|
||||
|
||||
if time.time() - t0 >= run_seconds:
|
||||
break
|
||||
|
||||
time.sleep(2)
|
||||
except KeyboardInterrupt:
|
||||
print("Interrupted, stopping...")
|
||||
finally:
|
||||
print("Stopping controller...")
|
||||
c.stop()
|
||||
print("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
unilabos/devices/cameraSII/demo_camera_pic.py
Normal file
36
unilabos/devices/cameraSII/demo_camera_pic.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import cv2
|
||||
|
||||
# 推荐把 @ 进行 URL 编码:@ -> %40
|
||||
RTSP_URL = "rtsp://admin:admin123@192.168.31.164:554/stream1"
|
||||
OUTPUT_IMAGE = "rtsp_test_frame.jpg"
|
||||
|
||||
def main():
|
||||
print(f"尝试连接 RTSP 流: {RTSP_URL}")
|
||||
cap = cv2.VideoCapture(RTSP_URL)
|
||||
|
||||
if not cap.isOpened():
|
||||
print("错误:无法打开 RTSP 流,请检查:")
|
||||
print(" 1. IP/端口是否正确")
|
||||
print(" 2. 账号密码(尤其是 @ 是否已转成 %40)是否正确")
|
||||
print(" 3. 摄像头是否允许当前主机访问(同一网段、防火墙等)")
|
||||
return
|
||||
|
||||
print("连接成功,开始读取一帧...")
|
||||
ret, frame = cap.read()
|
||||
|
||||
if not ret or frame is None:
|
||||
print("错误:已连接但未能读取到帧数据(可能是码流未开启或网络抖动)")
|
||||
cap.release()
|
||||
return
|
||||
|
||||
# 保存当前帧
|
||||
success = cv2.imwrite(OUTPUT_IMAGE, frame)
|
||||
cap.release()
|
||||
|
||||
if success:
|
||||
print(f"成功截取一帧并保存为: {OUTPUT_IMAGE}")
|
||||
else:
|
||||
print("错误:写入图片失败,请检查磁盘权限/路径")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
21
unilabos/devices/cameraSII/demo_camera_push.py
Normal file
21
unilabos/devices/cameraSII/demo_camera_push.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# run_camera_push.py
|
||||
import time
|
||||
from cameraDriver import CameraController # 这里根据你的文件名调整
|
||||
|
||||
if __name__ == "__main__":
|
||||
controller = CameraController(
|
||||
host_id="demo-host",
|
||||
signal_backend_url="wss://sciol.ac.cn/api/realtime/signal/host",
|
||||
rtmp_url="rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
||||
webrtc_api="https://srs.sciol.ac.cn/rtc/v1/play/",
|
||||
webrtc_stream_url="webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
||||
camera_rtsp_url="rtsp://admin:admin123@192.168.31.164:554/stream1",
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
status = controller.get_status()
|
||||
print(status)
|
||||
time.sleep(5)
|
||||
except KeyboardInterrupt:
|
||||
controller.stop()
|
||||
78
unilabos/devices/cameraSII/ptz_cameracontroller_test.py
Normal file
78
unilabos/devices/cameraSII/ptz_cameracontroller_test.py
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
使用 CameraController 来测试 PTZ:
|
||||
让摄像头按顺序向下、向上、向左、向右运动几次。
|
||||
"""
|
||||
|
||||
import time
|
||||
import sys
|
||||
|
||||
# 根据你的工程结构修改导入路径:
|
||||
# 假设 CameraController 定义在 cameraController.py 里
|
||||
from cameraDriver import CameraController
|
||||
|
||||
|
||||
def main():
|
||||
# === 根据你的实际情况填 IP、端口、账号密码 ===
|
||||
ptz_host = "192.168.31.164"
|
||||
ptz_port = 2020 # 注意要和你单独测试 PTZController 时保持一致
|
||||
ptz_user = "admin"
|
||||
ptz_password = "admin123"
|
||||
|
||||
# 1. 创建 CameraController 实例
|
||||
cam = CameraController(
|
||||
# 其他摄像机相关参数按你类的 __init__ 来补充
|
||||
ptz_host=ptz_host,
|
||||
ptz_port=ptz_port,
|
||||
ptz_user=ptz_user,
|
||||
ptz_password=ptz_password,
|
||||
)
|
||||
|
||||
# 2. 启动 / 初始化(如果你的 CameraController 有 start(config) 之类的接口)
|
||||
# 这里给一个最小的 config,重点是 PTZ 相关字段
|
||||
config = {
|
||||
"ptz_host": ptz_host,
|
||||
"ptz_port": ptz_port,
|
||||
"ptz_user": ptz_user,
|
||||
"ptz_password": ptz_password,
|
||||
}
|
||||
|
||||
try:
|
||||
cam.start(config)
|
||||
except Exception as e:
|
||||
print(f"[TEST] CameraController start() 失败: {e}", file=sys.stderr)
|
||||
return
|
||||
|
||||
# 这里可以判断一下内部 _ptz 是否初始化成功(如果你对 CameraController 做了封装)
|
||||
if getattr(cam, "_ptz", None) is None:
|
||||
print("[TEST] CameraController 内部 PTZ 未初始化成功,请检查 ptz_host/port/user/password 配置。", file=sys.stderr)
|
||||
return
|
||||
|
||||
# 3. 依次调用 CameraController 的 PTZ 方法
|
||||
# 这里假设你在 CameraController 中提供了这几个对外方法:
|
||||
# ptz_move_down / ptz_move_up / ptz_move_left / ptz_move_right
|
||||
# 如果你命名不一样,把下面调用名改成你的即可。
|
||||
|
||||
print("向下移动(通过 CameraController)...")
|
||||
cam.ptz_move_down(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("向上移动(通过 CameraController)...")
|
||||
cam.ptz_move_up(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("向左移动(通过 CameraController)...")
|
||||
cam.ptz_move_left(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("向右移动(通过 CameraController)...")
|
||||
cam.ptz_move_right(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("测试结束。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
50
unilabos/devices/cameraSII/ptz_test.py
Normal file
50
unilabos/devices/cameraSII/ptz_test.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
测试 cameraDriver.py中的 PTZController 类,让摄像头按顺序运动几次
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from cameraDriver import PTZController
|
||||
|
||||
|
||||
def main():
|
||||
# 根据你的实际情况填 IP、端口、账号密码
|
||||
host = "192.168.31.164"
|
||||
port = 80
|
||||
user = "admin"
|
||||
password = "admin123"
|
||||
|
||||
ptz = PTZController(host=host, port=port, user=user, password=password)
|
||||
|
||||
# 1. 连接摄像头
|
||||
if not ptz.connect():
|
||||
print("连接 PTZ 失败,检查 IP/用户名/密码/端口。")
|
||||
return
|
||||
|
||||
# 2. 依次测试几个动作
|
||||
# 每个动作之间 sleep 一下方便观察
|
||||
|
||||
print("向下移动...")
|
||||
ptz.move_down(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("向上移动...")
|
||||
ptz.move_up(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("向左移动...")
|
||||
ptz.move_left(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("向右移动...")
|
||||
ptz.move_right(speed=0.5, duration=1.0)
|
||||
time.sleep(1)
|
||||
|
||||
print("测试结束。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
954
unilabos/devices/liquid_handling/prcxi/base_material.json
Normal file
954
unilabos/devices/liquid_handling/prcxi/base_material.json
Normal file
@@ -0,0 +1,954 @@
|
||||
[
|
||||
{
|
||||
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
|
||||
"Code": "ZX-58-1250",
|
||||
"Name": "Tip头适配器 1250uL",
|
||||
"SummaryName": "Tip头适配器 1250uL",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 128,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 20,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 0,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "/images/20220624015044.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 10,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 16:03:52.6583727",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-24 13:50:44.8123474",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "7c822592b360451fb59690e49ac6b181",
|
||||
"Code": "ZX-58-300",
|
||||
"Name": "ZHONGXI 适配器 300uL",
|
||||
"SummaryName": "ZHONGXI 适配器 300uL",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 127,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 81,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 0,
|
||||
"Volume": 300,
|
||||
"ImagePath": "/images/20220623102838.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 10,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 16:07:53.7453351",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-23 10:28:38.6190575",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
|
||||
"Code": "ZX-58-10",
|
||||
"Name": "吸头10ul 适配器",
|
||||
"SummaryName": "吸头10ul 适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 128,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 72.3,
|
||||
"DepthNum": 0,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 127,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 10,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 16:37:40.7073733",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-05-30 15:17:01.8231737",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 0,
|
||||
"YSpacing": 0,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "7960f49ddfe9448abadda89bd1556936",
|
||||
"Code": "ZX-001-1250",
|
||||
"Name": "1250μL Tip头",
|
||||
"SummaryName": "1250μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 118.09,
|
||||
"WidthNum": 80.7,
|
||||
"HeightNum": 107.67,
|
||||
"DepthNum": 100,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 7.95,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "/images/20220623102536.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 96,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:53:27.8591195",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-23 10:25:36.2592442",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
|
||||
"Code": "ZX-001-10",
|
||||
"Name": "10μL Tip头",
|
||||
"SummaryName": "10μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 120.98,
|
||||
"WidthNum": 82.12,
|
||||
"HeightNum": 67,
|
||||
"DepthNum": 39.1,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 5,
|
||||
"Volume": 10,
|
||||
"ImagePath": "/images/20221119041031.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": -21,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:56:53.462015",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-19 16:10:31.126801",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
||||
"Code": "ZX-001-10+",
|
||||
"Name": "10μL加长 Tip头",
|
||||
"SummaryName": "10μL加长 Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 122.11,
|
||||
"WidthNum": 80.05,
|
||||
"HeightNum": 58.23,
|
||||
"DepthNum": 45.1,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 60,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 7,
|
||||
"Volume": 10,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 42,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:57:57.331211",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:02:51.2070383",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 1,
|
||||
"Margins_X": 7.97,
|
||||
"Margins_Y": 5
|
||||
},
|
||||
{
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SummaryName": "1000μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 128.09,
|
||||
"WidthNum": 85.8,
|
||||
"HeightNum": 98,
|
||||
"DepthNum": 88,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 100,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 7.95,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 47,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:59:20.5534915",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-05-30 14:49:53.639727",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 1,
|
||||
"Margins_X": 14.5,
|
||||
"Margins_Y": 11.4
|
||||
},
|
||||
{
|
||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
||||
"Code": "ZX-001-300",
|
||||
"Name": "300μL Tip头",
|
||||
"SummaryName": "300μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 122.11,
|
||||
"WidthNum": 80.05,
|
||||
"HeightNum": 58.23,
|
||||
"DepthNum": 45.1,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 60,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 7,
|
||||
"Volume": 300,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 11,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:00:24.7266192",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:02:40.6676947",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 1,
|
||||
"Margins_X": 7.97,
|
||||
"Margins_Y": 5
|
||||
},
|
||||
{
|
||||
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
|
||||
"Code": "ZX-001-200",
|
||||
"Name": "200μL Tip头",
|
||||
"SummaryName": "200μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 120.98,
|
||||
"WidthNum": 82.12,
|
||||
"HeightNum": 66.9,
|
||||
"DepthNum": 52,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 30,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 5.5,
|
||||
"Volume": 200,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 19,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:01:17.626704",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-05-27 11:42:24.6021522",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
||||
"Code": "ZX-023-0.2",
|
||||
"Name": "0.2ml PCR板",
|
||||
"SummaryName": "0.2ml PCR板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 126,
|
||||
"WidthNum": 86,
|
||||
"HeightNum": 21.2,
|
||||
"DepthNum": 15.17,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 6,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": -12,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:06:02.7746392",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2024-02-20 16:17:16.7921748",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "2.2ml 深孔板",
|
||||
"SummaryName": "2.2ml 深孔板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 127.3,
|
||||
"WidthNum": 85.35,
|
||||
"HeightNum": 44,
|
||||
"DepthNum": 42,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 8.2,
|
||||
"Volume": 2200,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 34,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:07:16.4538022",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:11:26.3993472",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
"Code": "ZX-58-10000",
|
||||
"Name": "储液槽",
|
||||
"SummaryName": "储液槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 125.02,
|
||||
"WidthNum": 82.97,
|
||||
"HeightNum": 31.2,
|
||||
"DepthNum": 24,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 99.33,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": -172,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-31 18:37:56.7949909",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:22:22.8543991",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 8.5,
|
||||
"Margins_Y": 5.5
|
||||
},
|
||||
{
|
||||
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
|
||||
"Code": "ZX-58-0001",
|
||||
"Name": "全裙边 PCR适配器",
|
||||
"SummaryName": "全裙边 PCR适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 125.42,
|
||||
"WidthNum": 83.13,
|
||||
"HeightNum": 15.69,
|
||||
"DepthNum": 13.41,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 5.1,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 100,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-01-02 19:21:35.8664843",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:14:36.1210193",
|
||||
"IsStright": 1,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 3,
|
||||
"Margins_X": 9.78,
|
||||
"Margins_Y": 7.72
|
||||
},
|
||||
{
|
||||
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
|
||||
"Code": "ZX-ADP-001",
|
||||
"Name": "储液槽 适配器",
|
||||
"SummaryName": "储液槽 适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 133,
|
||||
"WidthNum": 91.8,
|
||||
"HeightNum": 70,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 1,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-02-16 17:31:26.413594",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:10:58.786996",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 0,
|
||||
"YSpacing": 0,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
|
||||
"Code": "ZX-002-300",
|
||||
"Name": "300ul深孔板适配器",
|
||||
"SummaryName": "300ul深孔板适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 136.4,
|
||||
"WidthNum": 93.8,
|
||||
"HeightNum": 96,
|
||||
"DepthNum": 7,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 8.1,
|
||||
"Volume": 300,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-06-18 15:17:42.7917763",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:10:46.1526635",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
|
||||
"Code": "ZX-002-10",
|
||||
"Name": "10ul专用深孔板适配器",
|
||||
"SummaryName": "10ul专用深孔板适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 136.5,
|
||||
"WidthNum": 93.8,
|
||||
"HeightNum": 121.5,
|
||||
"DepthNum": 7,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 8.1,
|
||||
"Volume": 10,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-06-30 09:37:31.0451435",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:10:38.5409878",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "b01627718d3341aba649baa81c2c083c",
|
||||
"Code": "Sd155",
|
||||
"Name": "爱津",
|
||||
"SummaryName": "爱津",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 125,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 64,
|
||||
"DepthNum": 45.5,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 4,
|
||||
"Volume": 20,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-11-07 08:56:30.1794274",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-07 09:00:29.5496845",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
|
||||
"Code": "Fhh478",
|
||||
"Name": "适配器",
|
||||
"SummaryName": "适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 120,
|
||||
"WidthNum": 90,
|
||||
"HeightNum": 86,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 4,
|
||||
"Volume": 1000,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-11-07 09:00:10.7579131",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-07 09:00:10.7579134",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "730067cf07ae43849ddf4034299030e9",
|
||||
"Code": "q1",
|
||||
"Name": "废弃槽",
|
||||
"SummaryName": "废弃槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 126.59,
|
||||
"WidthNum": 84.87,
|
||||
"HeightNum": 103.17,
|
||||
"DepthNum": 80,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"HoleDiameter": 1,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-14 13:15:45.8172852",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:06:18.3331101",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 1,
|
||||
"YSpacing": 1,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 2.29,
|
||||
"Margins_Y": 2.64
|
||||
},
|
||||
{
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
"Code": "q2",
|
||||
"Name": "96深孔板",
|
||||
"SummaryName": "96深孔板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 127.3,
|
||||
"WidthNum": 85.35,
|
||||
"HeightNum": 44,
|
||||
"DepthNum": 42,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": 1,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 8.2,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-14 13:19:55.7225524",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-07-03 17:28:59.0082394",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 15,
|
||||
"Margins_Y": 10
|
||||
},
|
||||
{
|
||||
"uuid": "853dcfb6226f476e8b23c250217dc7da",
|
||||
"Code": "q3",
|
||||
"Name": "384板",
|
||||
"SummaryName": "384板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 126.6,
|
||||
"WidthNum": 84,
|
||||
"HeightNum": 9.4,
|
||||
"DepthNum": 8,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 24,
|
||||
"HoleRow": 16,
|
||||
"HoleDiameter": 3,
|
||||
"Volume": 1250,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-14 13:22:34.779818",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-10-14 13:22:34.7798181",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 4.5,
|
||||
"YSpacing": 4.5,
|
||||
"materialEnum": null,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
|
||||
"Code": "sdfrth654",
|
||||
"Name": "4道储液槽",
|
||||
"SummaryName": "4道储液槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 100,
|
||||
"WidthNum": 40,
|
||||
"HeightNum": 30,
|
||||
"DepthNum": 10,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 4,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 4,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2024-02-20 14:44:25.0021372",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-03-31 15:09:30.7392062",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 27,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b",
|
||||
"Code": "22",
|
||||
"Name": "48孔深孔板",
|
||||
"SummaryName": "48孔深孔板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "",
|
||||
"LengthNum": null,
|
||||
"WidthNum": null,
|
||||
"HeightNum": null,
|
||||
"DepthNum": null,
|
||||
"StandardHeight": null,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 6,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": null,
|
||||
"Volume": 23,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2025-03-19 09:38:09.8535874",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-03-19 09:38:09.8536386",
|
||||
"IsStright": null,
|
||||
"IsGeneral": null,
|
||||
"IsControl": null,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 18.5,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 2,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "0f1639987b154e1fac78f4fb29a1f7c1",
|
||||
"Code": "12道储液槽",
|
||||
"Name": "12道储液槽",
|
||||
"SummaryName": "12道储液槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "",
|
||||
"LengthNum": 129.5,
|
||||
"WidthNum": 83.047,
|
||||
"HeightNum": 30.6,
|
||||
"DepthNum": 26.7,
|
||||
"StandardHeight": null,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 8.04,
|
||||
"Volume": 12,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2025-05-21 13:10:53.2735971",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:20:40.4460256",
|
||||
"IsStright": null,
|
||||
"IsGeneral": null,
|
||||
"IsControl": null,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 8.7,
|
||||
"Margins_Y": 5.35
|
||||
},
|
||||
{
|
||||
"uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
|
||||
"Code": "HPLC01",
|
||||
"Name": "HPLC料盘",
|
||||
"SummaryName": "HPLC料盘",
|
||||
"SupplyType": 1,
|
||||
"Factory": "",
|
||||
"LengthNum": 0,
|
||||
"WidthNum": 0,
|
||||
"HeightNum": 0,
|
||||
"DepthNum": 0,
|
||||
"StandardHeight": null,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 7,
|
||||
"HoleRow": 15,
|
||||
"HoleDiameter": 0,
|
||||
"Volume": 1,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2025-07-12 17:10:43.2660127",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-07-12 17:10:43.2660131",
|
||||
"IsStright": null,
|
||||
"IsGeneral": null,
|
||||
"IsControl": null,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 12.5,
|
||||
"YSpacing": 16.5,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7",
|
||||
"Code": "1",
|
||||
"Name": "ep适配器",
|
||||
"SummaryName": "ep适配器",
|
||||
"SupplyType": 1,
|
||||
"Factory": "",
|
||||
"LengthNum": 128.04,
|
||||
"WidthNum": 85.8,
|
||||
"HeightNum": 42.66,
|
||||
"DepthNum": 38.08,
|
||||
"StandardHeight": null,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 6,
|
||||
"HoleRow": 4,
|
||||
"HoleDiameter": 10.6,
|
||||
"Volume": 1,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2025-09-03 13:31:54.1541015",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:18:03.8051993",
|
||||
"IsStright": null,
|
||||
"IsGeneral": null,
|
||||
"IsControl": null,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 21,
|
||||
"YSpacing": 18,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 3.54,
|
||||
"Margins_Y": 10.5
|
||||
},
|
||||
{
|
||||
"uuid": "a0757a90d8e44e81a68f306a608694f2",
|
||||
"Code": "ZX-58-30",
|
||||
"Name": "30mm适配器",
|
||||
"SummaryName": "30mm适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "",
|
||||
"LengthNum": 132,
|
||||
"WidthNum": 93.5,
|
||||
"HeightNum": 30,
|
||||
"DepthNum": 7,
|
||||
"StandardHeight": null,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 8.1,
|
||||
"Volume": 30,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2025-09-15 14:02:30.8094658",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-15 14:02:30.8098183",
|
||||
"IsStright": null,
|
||||
"IsGeneral": null,
|
||||
"IsControl": null,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 0,
|
||||
"Margins_X": 0,
|
||||
"Margins_Y": 0
|
||||
},
|
||||
{
|
||||
"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f",
|
||||
"Code": "ZX-78-096",
|
||||
"Name": "细菌培养皿",
|
||||
"SummaryName": "细菌培养皿",
|
||||
"SupplyType": 1,
|
||||
"Factory": "",
|
||||
"LengthNum": 124.09,
|
||||
"WidthNum": 81.89,
|
||||
"HeightNum": 13.67,
|
||||
"DepthNum": 11.2,
|
||||
"StandardHeight": null,
|
||||
"PipetteHeight": 0,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"HoleDiameter": 6.58,
|
||||
"Volume": 78,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2025-09-17 17:10:54.1859566",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2025-09-17 17:10:54.1859568",
|
||||
"IsStright": null,
|
||||
"IsGeneral": null,
|
||||
"IsControl": null,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": 4,
|
||||
"Margins_X": 9.28,
|
||||
"Margins_Y": 6.19
|
||||
}
|
||||
]
|
||||
@@ -156,7 +156,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300TipRack",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -4323,7 +4323,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -8297,7 +8297,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -8425,7 +8425,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -12496,7 +12496,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300TipRack",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -16664,7 +16664,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -20640,7 +20640,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -20671,7 +20671,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -20799,7 +20799,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -24872,7 +24872,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -28848,7 +28848,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -28879,7 +28879,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -29007,7 +29007,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -33080,7 +33080,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -37153,7 +37153,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 50,
|
||||
"size_z": 10,
|
||||
@@ -41151,6 +41151,5 @@
|
||||
"uuid": "730067cf07ae43849ddf4034299030e9"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,607 @@
|
||||
[
|
||||
{
|
||||
"Id": "1853794d-8cc1-4268-94b8-fc83e8be3ecc",
|
||||
"StartDosage": 1.0,
|
||||
"EndDosage": 55.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2126.89990234375,
|
||||
"B": 2085.300048828125,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 10
|
||||
},
|
||||
{
|
||||
"Id": "37a31398-499c-4df3-9bfe-ff92e6bc1427",
|
||||
"StartDosage": 1.0,
|
||||
"EndDosage": 303.0,
|
||||
"Aspiration": -1.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2229.6,
|
||||
"B": 3082.7,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "e602c693-e51c-4485-8788-beb3560e0599",
|
||||
"StartDosage": 303.0,
|
||||
"EndDosage": 400.0,
|
||||
"Aspiration": -0.8,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2156.6,
|
||||
"B": 9582.1,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "d7cdf777-ae58-46ab-b1ec-a5e59496bb8a",
|
||||
"StartDosage": 400.0,
|
||||
"EndDosage": 501.0,
|
||||
"Aspiration": -1.5,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2087.9,
|
||||
"B": 37256.0,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "6149a3a7-98fb-4270-83b4-4f21b5c4e8d8",
|
||||
"StartDosage": 501.0,
|
||||
"EndDosage": 600.0,
|
||||
"Aspiration": -1.5,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2185.0,
|
||||
"B": -12375.0,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "039f5735-a598-482d-b21d-b265d5e7436a",
|
||||
"StartDosage": 600.0,
|
||||
"EndDosage": 700.0,
|
||||
"Aspiration": -6.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2222.0,
|
||||
"B": -30370.0,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "80875977-ee0f-49f4-b10d-de429e57c5b8",
|
||||
"StartDosage": 700.0,
|
||||
"EndDosage": 800.0,
|
||||
"Aspiration": -6.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 1705.0,
|
||||
"B": 324436.0,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "a38afc7c-9c86-4014-a669-a7d159fb0c70",
|
||||
"StartDosage": 800.0,
|
||||
"EndDosage": 900.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2068.0,
|
||||
"B": 61331.0,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "a5ce0671-8767-4752-a04c-fdbdc3c7dc91",
|
||||
"StartDosage": 900.0,
|
||||
"EndDosage": 1001.0,
|
||||
"Aspiration": 3.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2047.2,
|
||||
"B": 78417.0,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "14daba17-0a35-474f-9f8a-e9ea6c355eb0",
|
||||
"StartDosage": 1.0,
|
||||
"EndDosage": 303.0,
|
||||
"Aspiration": -1.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2229.6,
|
||||
"B": 3082.7,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "82c2439c-79f6-4f61-9518-1b1205e44027",
|
||||
"StartDosage": 303.0,
|
||||
"EndDosage": 400.0,
|
||||
"Aspiration": -0.8,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2156.6,
|
||||
"B": 9582.1,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "7981db10-4005-4c62-a22d-fac90875e91c",
|
||||
"StartDosage": 400.0,
|
||||
"EndDosage": 501.0,
|
||||
"Aspiration": -1.5,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2087.9,
|
||||
"B": 37256.0,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "ae7606fd-98fa-4236-bec4-a4d60018dbea",
|
||||
"StartDosage": 501.0,
|
||||
"EndDosage": 600.0,
|
||||
"Aspiration": -1.5,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2185.0,
|
||||
"B": -12375.0,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "ed2a2db0-77b6-4a0a-ac36-7184f0b2c2c8",
|
||||
"StartDosage": 600.0,
|
||||
"EndDosage": 700.0,
|
||||
"Aspiration": -6.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2222.0,
|
||||
"B": -30370.0,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "ed639da4-b02f-4d2a-825d-b47cebdfbf1b",
|
||||
"StartDosage": 700.0,
|
||||
"EndDosage": 800.0,
|
||||
"Aspiration": -6.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 1705.0,
|
||||
"B": 324436.0,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "7e740c8a-1043-4db1-820f-2e6e77386d7f",
|
||||
"StartDosage": 800.0,
|
||||
"EndDosage": 900.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2068.0,
|
||||
"B": 61331.0,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "49b6c4fe-e11a-4056-8de7-fd9a2b81bc90",
|
||||
"StartDosage": 900.0,
|
||||
"EndDosage": 1001.0,
|
||||
"Aspiration": 3.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2047.2,
|
||||
"B": 78417.0,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "67dee69d-a2a9-4598-8d8d-98b211a58821",
|
||||
"StartDosage": 1.0,
|
||||
"EndDosage": 6.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 20211.0,
|
||||
"B": 10779.0,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 50
|
||||
},
|
||||
{
|
||||
"Id": "d5c1b2b0-f897-4873-86bf-0ce5f443dfd3",
|
||||
"StartDosage": 6.0,
|
||||
"EndDosage": 25.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 20211.0,
|
||||
"B": 10779.0,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 50
|
||||
},
|
||||
{
|
||||
"Id": "b2789b53-6e0e-4b83-9932-f41c83d10da8",
|
||||
"StartDosage": 25.0,
|
||||
"EndDosage": 50.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 20015.0,
|
||||
"B": 17507.0,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 50
|
||||
},
|
||||
{
|
||||
"Id": "1f0d0bbb-6ea2-4d19-8452-6824fa1f474c",
|
||||
"StartDosage": 0.1,
|
||||
"EndDosage": 5.0,
|
||||
"Aspiration": -1.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 1981.1,
|
||||
"B": 3498.1,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "c58111db-dadc-43bd-97b3-a596f441d704",
|
||||
"StartDosage": 5.0,
|
||||
"EndDosage": 10.0,
|
||||
"Aspiration": -1.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2113.3,
|
||||
"B": 2810.8,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "a15fd33d-28cd-4bca-bd6c-018e3bafcb65",
|
||||
"StartDosage": 10.0,
|
||||
"EndDosage": 50.0,
|
||||
"Aspiration": -0.8,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2113.3,
|
||||
"B": 2810.8,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "ab957383-d83d-4fcc-8373-9d8f415c3023",
|
||||
"StartDosage": 50.0,
|
||||
"EndDosage": 100.0,
|
||||
"Aspiration": -0.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2093.7,
|
||||
"B": 2969.2,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "be6b6f79-222f-4f6f-ae73-e537f397a11e",
|
||||
"StartDosage": 100.0,
|
||||
"EndDosage": 150.0,
|
||||
"Aspiration": 1.7,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2093.7,
|
||||
"B": 2969.2,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "0ab3fc05-8f9f-4dc0-a2ce-918ade17810c",
|
||||
"StartDosage": 150.0,
|
||||
"EndDosage": 200.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "43b82710-37df-4039-9513-aa49bc5bc607",
|
||||
"StartDosage": 200.0,
|
||||
"EndDosage": 250.0,
|
||||
"Aspiration": 4.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "2f208ffc-808f-4bf9-b443-14dbf0338d83",
|
||||
"StartDosage": 250.0,
|
||||
"EndDosage": 310.0,
|
||||
"Aspiration": 5.3,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "84bb5356-481d-41b9-a563-917e64b5e20c",
|
||||
"StartDosage": 1.0,
|
||||
"EndDosage": 10.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 964.19,
|
||||
"B": 1207.7,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "67463c2c-a520-4d33-831f-e0c3cdcdec60",
|
||||
"StartDosage": 10.0,
|
||||
"EndDosage": 50.0,
|
||||
"Aspiration": 0.5,
|
||||
"Dispensing": 0.0,
|
||||
"K": 964.19,
|
||||
"B": 1207.7,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "a752d77e-7c5d-450a-8b54-e87513facda0",
|
||||
"StartDosage": 50.0,
|
||||
"EndDosage": 100.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 964.19,
|
||||
"B": 1207.7,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "d30f522a-5992-4be4-984d-0c27b9e8f410",
|
||||
"StartDosage": 100.0,
|
||||
"EndDosage": 300.0,
|
||||
"Aspiration": 1.8,
|
||||
"Dispensing": 0.0,
|
||||
"K": 937.8,
|
||||
"B": 3550.1,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "29914cbe-ad35-4712-80b1-8c4e54f9fc15",
|
||||
"StartDosage": 300.0,
|
||||
"EndDosage": 500.0,
|
||||
"Aspiration": 2.5,
|
||||
"Dispensing": 0.0,
|
||||
"K": 937.8,
|
||||
"B": 3550.1,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "b75b1d6d-9b53-4b5c-b6ab-640cb23491d8",
|
||||
"StartDosage": 500.0,
|
||||
"EndDosage": 800.0,
|
||||
"Aspiration": 50.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 928.69,
|
||||
"B": 8253.7,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "1658a9de-bb62-4dd6-9715-0e8e71b27f97",
|
||||
"StartDosage": 800.0,
|
||||
"EndDosage": 900.0,
|
||||
"Aspiration": 4.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 928.69,
|
||||
"B": 8253.7,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "4d0fec65-983d-47f6-82fe-723bb9efd42a",
|
||||
"StartDosage": 900.0,
|
||||
"EndDosage": 1050.0,
|
||||
"Aspiration": 5.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 928.69,
|
||||
"B": 8253.7,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 1000
|
||||
},
|
||||
{
|
||||
"Id": "f194ad17-3be3-4684-bf21-d458693e640c",
|
||||
"StartDosage": 1.0,
|
||||
"EndDosage": 2.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 62616.0,
|
||||
"B": 106.49,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 10
|
||||
},
|
||||
{
|
||||
"Id": "fa43155c-8220-4ead-bc8f-6984a25711bf",
|
||||
"StartDosage": 2.0,
|
||||
"EndDosage": 7.0,
|
||||
"Aspiration": -0.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 52421.0,
|
||||
"B": 20977.0,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 10
|
||||
},
|
||||
{
|
||||
"Id": "9b05eebb-ba5d-427c-bd4f-1b6745bab932",
|
||||
"StartDosage": 7.0,
|
||||
"EndDosage": 11.0,
|
||||
"Aspiration": 0.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 51942.0,
|
||||
"B": 21434.0,
|
||||
"compensateEnum": 5,
|
||||
"materialVolume": 10
|
||||
},
|
||||
{
|
||||
"Id": "d4715f09-e24a-4ed2-b784-09256640bcf7",
|
||||
"StartDosage": 0.5,
|
||||
"EndDosage": 5.0,
|
||||
"Aspiration": -1.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 1981.1,
|
||||
"B": 3498.1,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "e37e2fad-954d-4a17-8312-e08bbde00902",
|
||||
"StartDosage": 5.0,
|
||||
"EndDosage": 10.0,
|
||||
"Aspiration": -1.1,
|
||||
"Dispensing": -0.8,
|
||||
"K": 2113.3,
|
||||
"B": 2810.8,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "642714bd-22c6-46b5-9a48-2f0bcd91d555",
|
||||
"StartDosage": 10.0,
|
||||
"EndDosage": 50.0,
|
||||
"Aspiration": -0.8,
|
||||
"Dispensing": -2.0,
|
||||
"K": 2113.3,
|
||||
"B": 2810.8,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "2fccf79f-52e5-4b6c-be6e-bdac167dd40c",
|
||||
"StartDosage": 50.0,
|
||||
"EndDosage": 100.0,
|
||||
"Aspiration": -0.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2093.7,
|
||||
"B": 2969.2,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "34555f2c-2e11-4c45-b733-83a8185727da",
|
||||
"StartDosage": 100.0,
|
||||
"EndDosage": 150.0,
|
||||
"Aspiration": 1.7,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2093.7,
|
||||
"B": 2969.2,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "9353ac79-b710-49da-a423-4bfe651ac16a",
|
||||
"StartDosage": 150.0,
|
||||
"EndDosage": 200.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "1628da53-8c86-4eff-b119-07cb7a859bb6",
|
||||
"StartDosage": 200.0,
|
||||
"EndDosage": 250.0,
|
||||
"Aspiration": 4.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "658913c3-2c3e-4e14-9eb3-0489b5fdee7f",
|
||||
"StartDosage": 250.0,
|
||||
"EndDosage": 310.0,
|
||||
"Aspiration": -11.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 7,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "f736e716-ec13-432c-ac2e-4905753ac6f9",
|
||||
"StartDosage": 0.1,
|
||||
"EndDosage": 5.0,
|
||||
"Aspiration": -1.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 1981.1,
|
||||
"B": 3498.1,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "7595eda8-f2d8-491f-bdac-69d169308ab5",
|
||||
"StartDosage": 5.0,
|
||||
"EndDosage": 10.0,
|
||||
"Aspiration": -1.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2113.3,
|
||||
"B": 2810.8,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "42eddd0a-8394-4245-8ad3-49573b25286e",
|
||||
"StartDosage": 10.0,
|
||||
"EndDosage": 50.0,
|
||||
"Aspiration": -0.8,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2113.3,
|
||||
"B": 2810.8,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "713eadfe-25c0-4ec0-acfd-900df9e12396",
|
||||
"StartDosage": 50.0,
|
||||
"EndDosage": 100.0,
|
||||
"Aspiration": -0.1,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2093.7,
|
||||
"B": 2969.2,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "f602c7bd-bdcf-4be0-9d77-a16d409bc64b",
|
||||
"StartDosage": 100.0,
|
||||
"EndDosage": 150.0,
|
||||
"Aspiration": 1.7,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2093.7,
|
||||
"B": 2969.2,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "b91867e5-f0a2-4bbe-b37e-aec9837b019e",
|
||||
"StartDosage": 150.0,
|
||||
"EndDosage": 200.0,
|
||||
"Aspiration": 0.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "bd2e39d7-eb93-4d40-b0b4-2aac6b5678f3",
|
||||
"StartDosage": 200.0,
|
||||
"EndDosage": 250.0,
|
||||
"Aspiration": 4.0,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
},
|
||||
{
|
||||
"Id": "52e20b7f-f519-434f-86bb-a48238c290d1",
|
||||
"StartDosage": 250.0,
|
||||
"EndDosage": 310.0,
|
||||
"Aspiration": 5.3,
|
||||
"Dispensing": 0.0,
|
||||
"K": 2085.0,
|
||||
"B": 3548.3,
|
||||
"compensateEnum": 6,
|
||||
"materialVolume": 300
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,794 @@
|
||||
[
|
||||
{
|
||||
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
|
||||
"Code": "ZX-58-1250",
|
||||
"Name": "Tip头适配器 1250uL",
|
||||
"SummaryName": "Tip头适配器 1250uL",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 128,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 20,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 0,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "/images/20220624015044.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 10,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 16:03:52.6583727",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-24 13:50:44.8123474",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "7c822592b360451fb59690e49ac6b181",
|
||||
"Code": "ZX-58-300",
|
||||
"Name": "ZHONGXI 适配器 300uL",
|
||||
"SummaryName": "ZHONGXI 适配器 300uL",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 127,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 81,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 0,
|
||||
"Volume": 300,
|
||||
"ImagePath": "/images/20220623102838.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 10,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 16:07:53.7453351",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-23 10:28:38.6190575",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
|
||||
"Code": "ZX-58-10",
|
||||
"Name": "吸头10ul 适配器",
|
||||
"SummaryName": "吸头10ul 适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 128,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 81,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 127,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "/images/20221115010348.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 10,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 16:37:40.7073733",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-15 13:03:48.1679642",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "7960f49ddfe9448abadda89bd1556936",
|
||||
"Code": "ZX-001-1250",
|
||||
"Name": "1250μL Tip头",
|
||||
"SummaryName": "1250μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 118.09,
|
||||
"WidthNum": 80.7,
|
||||
"HeightNum": 107.67,
|
||||
"DepthNum": 100,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 7.95,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "/images/20220623102536.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 96,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:53:27.8591195",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-23 10:25:36.2592442",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
|
||||
"Code": "ZX-001-10",
|
||||
"Name": "10μL Tip头",
|
||||
"SummaryName": "10μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 120.98,
|
||||
"WidthNum": 82.12,
|
||||
"HeightNum": 67,
|
||||
"DepthNum": 39.1,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 5,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "/images/20221119041031.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": -21,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:56:53.462015",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-19 16:10:31.126801",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
||||
"Code": "ZX-001-10+",
|
||||
"Name": "10μL加长 Tip头",
|
||||
"SummaryName": "10μL加长 Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 120.98,
|
||||
"WidthNum": 82.12,
|
||||
"HeightNum": 50.3,
|
||||
"DepthNum": 45.8,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 5,
|
||||
"Volume": 20,
|
||||
"ImagePath": "/images/20220718120113.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 42,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:57:57.331211",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-07-18 12:01:13.2131453",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SummaryName": "1000μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 118.09,
|
||||
"WidthNum": 80.7,
|
||||
"HeightNum": 107.67,
|
||||
"DepthNum": 88,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 7.95,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 47,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 20:59:20.5534915",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:11:44.8670189",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
||||
"Code": "ZX-001-300",
|
||||
"Name": "300μL Tip头",
|
||||
"SummaryName": "300μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 120.98,
|
||||
"WidthNum": 82.12,
|
||||
"HeightNum": 40,
|
||||
"DepthNum": 59.3,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 5.5,
|
||||
"Volume": 300,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 11,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:00:24.7266192",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2024-02-01 15:48:02.1562734",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
|
||||
"Code": "ZX-001-200",
|
||||
"Name": "200μL Tip头",
|
||||
"SummaryName": "200μL Tip头",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 120.98,
|
||||
"WidthNum": 82.12,
|
||||
"HeightNum": 66.9,
|
||||
"DepthNum": 52,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 5.5,
|
||||
"Volume": 200,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 19,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:01:17.626704",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-10-14 13:44:41.5428946",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
||||
"Code": "ZX-023-0.2",
|
||||
"Name": "0.2ml PCR板",
|
||||
"SummaryName": "0.2ml PCR板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 126,
|
||||
"WidthNum": 86,
|
||||
"HeightNum": 21.2,
|
||||
"DepthNum": 15.17,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 96,
|
||||
"HoleDiameter": 6,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": -12,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:06:02.7746392",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2024-02-20 16:17:16.7921748",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "2.2ml 深孔板",
|
||||
"SummaryName": "2.2ml 深孔板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 127.3,
|
||||
"WidthNum": 85.35,
|
||||
"HeightNum": 44,
|
||||
"DepthNum": 42,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 8.2,
|
||||
"Volume": 2200,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": 34,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-30 21:07:16.4538022",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:11:26.3993472",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
"Code": "ZX-58-10000",
|
||||
"Name": "储液槽",
|
||||
"SummaryName": "储液槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 127,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 31.2,
|
||||
"DepthNum": 24,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 127,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "/images/20220623103134.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": -172,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2021-12-31 18:37:56.7949909",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-06-23 10:31:34.4261358",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
|
||||
"Code": "ZX-58-0001",
|
||||
"Name": "半裙边 PCR适配器",
|
||||
"SummaryName": "半裙边 PCR适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 127,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 88,
|
||||
"DepthNum": 5,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 96,
|
||||
"HoleDiameter": 9,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "/images/20221123051800.jpg",
|
||||
"QRCode": null,
|
||||
"Qty": 100,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-01-02 19:21:35.8664843",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-23 17:18:00.8826719",
|
||||
"IsStright": 1,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 1,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
|
||||
"Code": "ZX-ADP-001",
|
||||
"Name": "储液槽 适配器",
|
||||
"SummaryName": "储液槽 适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 133,
|
||||
"WidthNum": 91.8,
|
||||
"HeightNum": 70,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 8,
|
||||
"HoleDiameter": 1,
|
||||
"Volume": 1250,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-02-16 17:31:26.413594",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:10:58.786996",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 0,
|
||||
"YSpacing": 0,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
|
||||
"Code": "ZX-002-300",
|
||||
"Name": "300ul深孔板适配器",
|
||||
"SummaryName": "300ul深孔板适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 136.4,
|
||||
"WidthNum": 93.8,
|
||||
"HeightNum": 96,
|
||||
"DepthNum": 7,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 96,
|
||||
"HoleDiameter": 8.1,
|
||||
"Volume": 300,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-06-18 15:17:42.7917763",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:10:46.1526635",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
|
||||
"Code": "ZX-002-10",
|
||||
"Name": "10ul专用深孔板适配器",
|
||||
"SummaryName": "10ul专用深孔板适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "宁静致远",
|
||||
"LengthNum": 136.5,
|
||||
"WidthNum": 93.8,
|
||||
"HeightNum": 121.5,
|
||||
"DepthNum": 7,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 96,
|
||||
"HoleDiameter": 8.1,
|
||||
"Volume": 10,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-06-30 09:37:31.0451435",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-08-12 13:10:38.5409878",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "b01627718d3341aba649baa81c2c083c",
|
||||
"Code": "Sd155",
|
||||
"Name": "爱津",
|
||||
"SummaryName": "爱津",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 125,
|
||||
"WidthNum": 85,
|
||||
"HeightNum": 64,
|
||||
"DepthNum": 45.5,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 4,
|
||||
"Volume": 20,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-11-07 08:56:30.1794274",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-07 09:00:29.5496845",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
|
||||
"Code": "Fhh478",
|
||||
"Name": "适配器",
|
||||
"SummaryName": "适配器",
|
||||
"SupplyType": 2,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 120,
|
||||
"WidthNum": 90,
|
||||
"HeightNum": 86,
|
||||
"DepthNum": 4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 4,
|
||||
"Volume": 1000,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2022-11-07 09:00:10.7579131",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2022-11-07 09:00:10.7579134",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": null,
|
||||
"YSpacing": null,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "1592e84a07f74668af155588867f2da7",
|
||||
"Code": "12",
|
||||
"Name": "12",
|
||||
"SummaryName": "12",
|
||||
"SupplyType": 1,
|
||||
"Factory": "12",
|
||||
"LengthNum": 1,
|
||||
"WidthNum": 1,
|
||||
"HeightNum": 1,
|
||||
"DepthNum": 100,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 8,
|
||||
"HoleRow": 12,
|
||||
"ChannelNum": 12,
|
||||
"HoleDiameter": 7,
|
||||
"Volume": 12,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-08 09:35:19.281766",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-10-08 09:35:19.2817667",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 0,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "730067cf07ae43849ddf4034299030e9",
|
||||
"Code": "q1",
|
||||
"Name": "废弃槽",
|
||||
"SummaryName": "废弃槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 190,
|
||||
"WidthNum": 135,
|
||||
"HeightNum": 75,
|
||||
"DepthNum": 1,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 1,
|
||||
"HoleRow": 1,
|
||||
"ChannelNum": 1,
|
||||
"HoleDiameter": 1,
|
||||
"Volume": 1250,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-14 13:15:45.8172852",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-10-14 13:15:45.8172869",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 1,
|
||||
"YSpacing": 1,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
"Code": "q2",
|
||||
"Name": "96深孔板",
|
||||
"SummaryName": "96深孔板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 126.5,
|
||||
"WidthNum": 84.5,
|
||||
"HeightNum": 41.4,
|
||||
"DepthNum": 38.4,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 12,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 96,
|
||||
"HoleDiameter": 8.3,
|
||||
"Volume": 1250,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-14 13:19:55.7225524",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-10-14 13:19:55.7225525",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 9,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "853dcfb6226f476e8b23c250217dc7da",
|
||||
"Code": "q3",
|
||||
"Name": "384板",
|
||||
"SummaryName": "384板",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 126.6,
|
||||
"WidthNum": 84,
|
||||
"HeightNum": 9.4,
|
||||
"DepthNum": 8,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 24,
|
||||
"HoleRow": 16,
|
||||
"ChannelNum": 384,
|
||||
"HoleDiameter": 3,
|
||||
"Volume": 1250,
|
||||
"ImagePath": null,
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2023-10-14 13:22:34.779818",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2023-10-14 13:22:34.7798181",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 4.5,
|
||||
"YSpacing": 4.5,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "e201e206fcfc4e8ab51946a22e8cd1bc",
|
||||
"Code": "1",
|
||||
"Name": "ep",
|
||||
"SummaryName": "ep",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 504,
|
||||
"WidthNum": 337,
|
||||
"HeightNum": 160,
|
||||
"DepthNum": 163,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 6,
|
||||
"HoleRow": 4,
|
||||
"ChannelNum": 24,
|
||||
"HoleDiameter": 41.2,
|
||||
"Volume": 1,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2024-01-20 13:14:38.0308919",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2024-02-05 16:27:07.2582693",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 21,
|
||||
"YSpacing": 18,
|
||||
"materialEnum": null
|
||||
},
|
||||
{
|
||||
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
|
||||
"Code": "sdfrth654",
|
||||
"Name": "4道储液槽",
|
||||
"SummaryName": "4道储液槽",
|
||||
"SupplyType": 1,
|
||||
"Factory": "中析",
|
||||
"LengthNum": 100,
|
||||
"WidthNum": 40,
|
||||
"HeightNum": 30,
|
||||
"DepthNum": 10,
|
||||
"StandardHeight": 0,
|
||||
"PipetteHeight": null,
|
||||
"HoleColum": 4,
|
||||
"HoleRow": 8,
|
||||
"ChannelNum": 4,
|
||||
"HoleDiameter": 4,
|
||||
"Volume": 1000,
|
||||
"ImagePath": "",
|
||||
"QRCode": null,
|
||||
"Qty": null,
|
||||
"CreateName": null,
|
||||
"CreateTime": "2024-02-20 14:44:25.0021372",
|
||||
"UpdateName": null,
|
||||
"UpdateTime": "2024-02-20 15:28:21.3881302",
|
||||
"IsStright": 0,
|
||||
"IsGeneral": 1,
|
||||
"IsControl": 0,
|
||||
"ArmCode": null,
|
||||
"XSpacing": 27,
|
||||
"YSpacing": 9,
|
||||
"materialEnum": null
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,602 @@
|
||||
[
|
||||
{
|
||||
"uuid": "87ea11eeb24b43648ce294654b561fe7",
|
||||
"PlanName": "2341",
|
||||
"PlanCode": "2980eb",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-05-15 18:24:00.8445073",
|
||||
"MatrixId": "34ba3f02-6fcd-48e6-bb8e-3b0ce1d54ed5"
|
||||
},
|
||||
{
|
||||
"uuid": "0a977d6ebc4244739793b0b6f8b3f815",
|
||||
"PlanName": "384测试方案(300模块)",
|
||||
"PlanCode": "9336ee",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-06-13 10:34:52.5310959",
|
||||
"MatrixId": "74ed84ea-0b5d-4307-a966-ceb83fcaefe7"
|
||||
},
|
||||
{
|
||||
"uuid": "aff2cd213ad34072b370f44acb5ab658",
|
||||
"PlanName": "96孔吸300方案(单放)",
|
||||
"PlanCode": "9932fc",
|
||||
"PlanTarget": "测试用",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-06-13 09:57:38.422353",
|
||||
"MatrixId": "bacd78be-b86d-49d6-973a-dd522834e4c4"
|
||||
},
|
||||
{
|
||||
"uuid": "97816d94f99a48409379013d19f0ab66",
|
||||
"PlanName": "384测试方案(50模块)",
|
||||
"PlanCode": "3964de",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-06-13 10:32:22.8918817",
|
||||
"MatrixId": "74ed84ea-0b5d-4307-a966-ceb83fcaefe7"
|
||||
},
|
||||
{
|
||||
"uuid": "c3d86e9d7eed4ddb8c32e9234da659de",
|
||||
"PlanName": "96吸50方案(单放)",
|
||||
"PlanCode": "6994aa",
|
||||
"PlanTarget": "测试用",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-08-08 11:50:14.6850189",
|
||||
"MatrixId": "bacd78be-b86d-49d6-973a-dd522834e4c4"
|
||||
},
|
||||
{
|
||||
"uuid": "59a97f77718d4bbba6bed1ddbf959772",
|
||||
"PlanName": "test12",
|
||||
"PlanCode": "8630fa",
|
||||
"PlanTarget": "12通道",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-08 09:36:14.2536629",
|
||||
"MatrixId": "517c836e-56c6-4c06-a897-7074886061bd"
|
||||
},
|
||||
{
|
||||
"uuid": "84d50e4cf3034aa6a3de505a92b30812",
|
||||
"PlanName": "test001",
|
||||
"PlanCode": "9013fe",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-08 16:37:57.2302499",
|
||||
"MatrixId": "ed9b1ceb-b879-4b8c-a246-2d4f54fbe970"
|
||||
},
|
||||
{
|
||||
"uuid": "d052b893c6324ae38d301a58614a5663",
|
||||
"PlanName": "test01",
|
||||
"PlanCode": "8524cf",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-09 11:00:21.4973895",
|
||||
"MatrixId": "bacd78be-b86d-49d6-973a-dd522834e4c4"
|
||||
},
|
||||
{
|
||||
"uuid": "875a6eaa00e548b99318fd0be310e879",
|
||||
"PlanName": "test002",
|
||||
"PlanCode": "2477fe",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-09 11:02:01.2027308",
|
||||
"MatrixId": "7374dc89-d425-42aa-b252-1b1338d3c2f2"
|
||||
},
|
||||
{
|
||||
"uuid": "ecb3cb37f603495d95a93522a6b611e3",
|
||||
"PlanName": "test02",
|
||||
"PlanCode": "5126cb",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-09 11:02:14.7987877",
|
||||
"MatrixId": "7374dc89-d425-42aa-b252-1b1338d3c2f2"
|
||||
},
|
||||
{
|
||||
"uuid": "705edabbcbd645d0925e4e581643247c",
|
||||
"PlanName": "test003",
|
||||
"PlanCode": "4994cc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-09 11:41:04.1715458",
|
||||
"MatrixId": "4c126841-5c37-49c7-b4e8-539983bc9cc4"
|
||||
},
|
||||
{
|
||||
"uuid": "6c58136d7de54a6abb7b51e6327eacac",
|
||||
"PlanName": "test04",
|
||||
"PlanCode": "9704dd",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-09 11:51:59.1752071",
|
||||
"MatrixId": "4c126841-5c37-49c7-b4e8-539983bc9cc4"
|
||||
},
|
||||
{
|
||||
"uuid": "208f00a911b846d9922b2e72bdda978c",
|
||||
"PlanName": "96版位 50ul量程",
|
||||
"PlanCode": "7595be",
|
||||
"PlanTarget": "213213",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-18 19:12:17.4641981",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "40bd0ca25ffb4be6b246353db6ebefc9",
|
||||
"PlanName": "96版位 300ul量程",
|
||||
"PlanCode": "7421fc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-14 14:47:03.8105699",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "30b838bb7d124ec885b506df29ee7860",
|
||||
"PlanName": "300版位 50ul量程",
|
||||
"PlanCode": "6364cc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-14 14:48:05.2235254",
|
||||
"MatrixId": "f8c70333-b717-4ca0-9306-c40fd5f156fb"
|
||||
},
|
||||
{
|
||||
"uuid": "e53c591c86334c6f92d3b1afa107bcf8",
|
||||
"PlanName": "384版位 300ul量程",
|
||||
"PlanCode": "4029be",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-14 14:47:48.9478679",
|
||||
"MatrixId": "f8c70333-b717-4ca0-9306-c40fd5f156fb"
|
||||
},
|
||||
{
|
||||
"uuid": "1d26d1ab45c6431990ba0e00cc1f78d2",
|
||||
"PlanName": "96版位梯度稀释 50ul量程",
|
||||
"PlanCode": "3502cf",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-14 14:48:12.8676989",
|
||||
"MatrixId": "916bbd00-e66c-4237-9843-e049b70b740a"
|
||||
},
|
||||
{
|
||||
"uuid": "7a0383b4fbb543339723513228365451",
|
||||
"PlanName": "96版位梯度稀释 300ul量程",
|
||||
"PlanCode": "9345fe",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-10-14 14:50:02.0250566",
|
||||
"MatrixId": "916bbd00-e66c-4237-9843-e049b70b740a"
|
||||
},
|
||||
{
|
||||
"uuid": "69d4882f0f024fb5a3b91010f149ff89",
|
||||
"PlanName": "测试",
|
||||
"PlanCode": "3941bf",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2023-12-11 15:24:30.1371824",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "3603f89f4e0945f68353a33e8017ba6e",
|
||||
"PlanName": "测试111",
|
||||
"PlanCode": "8056eb",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 09:29:12.1441631",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "b44be8260740460598816c40f13fd6b4",
|
||||
"PlanName": "测试12",
|
||||
"PlanCode": "8272fb",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 10:40:54.2543702",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "f189a50122d54a568f3d39dc1f996167",
|
||||
"PlanName": "0.5",
|
||||
"PlanCode": "2093ec",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 13:06:37.8280696",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "b48218c8f2274b108e278d019c9b5126",
|
||||
"PlanName": "3",
|
||||
"PlanCode": "9493bb",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 14:20:42.4761092",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "41d2ebc5ab5b4b2da3e203937c5cbe70",
|
||||
"PlanName": "6",
|
||||
"PlanCode": "5586de",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 15:21:03.4440875",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "49ec03499aa646b9b8069a783dbeca1c",
|
||||
"PlanName": "7",
|
||||
"PlanCode": "1162bc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 15:31:33.7359724",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "a9c6d149cdf04636ac43cfb7623e4e7f",
|
||||
"PlanName": "8",
|
||||
"PlanCode": "7354eb",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 15:39:32.2399414",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "0e3a36cabefa4f5497e35193db48b559",
|
||||
"PlanName": "9",
|
||||
"PlanCode": "4453ba",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 15:49:31.5830134",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "d0a0d926e2034abc94b4d883951a78f7",
|
||||
"PlanName": "10",
|
||||
"PlanCode": "5797ab",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 16:00:25.4439315",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "22ac523a47e7421e80f401baf1526daf",
|
||||
"PlanName": "50",
|
||||
"PlanCode": "2507ca",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-16 16:23:13.8022807",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "fdea60f535ee4bc39c02c602a64f46bd",
|
||||
"PlanName": "11",
|
||||
"PlanCode": "1574ae",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 09:14:59.8230591",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "6650f7df6b8944f98476da92ce81d688",
|
||||
"PlanName": "12",
|
||||
"PlanCode": "2145bd",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 09:45:34.137906",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "9415a69280c042a09d6836f5eeddf40f",
|
||||
"PlanName": "100",
|
||||
"PlanCode": "2073fd",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 10:12:29.9998926",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "d9740fea94a04c2db44b1364a336b338",
|
||||
"PlanName": "250",
|
||||
"PlanCode": "2601ea",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 11:15:54.2583401",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "1d80c1fff5af442595c21963e6ca9fee",
|
||||
"PlanName": "160",
|
||||
"PlanCode": "6612ea",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 11:18:59.0457638",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "36889fb926aa480cb42de97700522bbf",
|
||||
"PlanName": "200",
|
||||
"PlanCode": "3174dc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 11:20:15.7676326",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "bd90ae2846c14e708854938158fd3443",
|
||||
"PlanName": "300",
|
||||
"PlanCode": "2665df",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 13:00:16.9242256",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "9df4857d2bef45bcad14cc13055e9f7b",
|
||||
"PlanName": "500",
|
||||
"PlanCode": "4771ab",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 13:26:32.3910805",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "d2f6e63cf1ff41a4a8d03f4444a2aeac",
|
||||
"PlanName": "800",
|
||||
"PlanCode": "4560bc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 13:42:35.5153947",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "f40a6f4326a346d39d5a82f6262aba47",
|
||||
"PlanName": "测试12345",
|
||||
"PlanCode": "3402ab",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 14:37:29.8890777",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "4248035f01e943faa6d71697ed386e19",
|
||||
"PlanName": "995",
|
||||
"PlanCode": "2688dc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-18 14:39:23.5292196",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "a73bc780e4d04099bf54c2b90fa7b974",
|
||||
"PlanName": "1000",
|
||||
"PlanCode": "2889bf",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 09:16:37.7818522",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "4d97363a0a334094a1ff24494a902d02",
|
||||
"PlanName": "2.。",
|
||||
"PlanCode": "6527ff",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 11:38:00.0672017",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "6eec360c74464769967ebefa43b7aec1",
|
||||
"PlanName": "2222222",
|
||||
"PlanCode": "8763ce",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 11:40:42.7038484",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "986049c83b054171a1b34dd49b3ca9cf",
|
||||
"PlanName": "9ul",
|
||||
"PlanCode": "1945fd",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 13:33:06.6556398",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "462eed73962142c2bd3b8fe717caceb6",
|
||||
"PlanName": "8ul",
|
||||
"PlanCode": "6912fc",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 15:16:17.4254316",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "b2f0c7ab462f4cf1bae56ee59a49a253",
|
||||
"PlanName": "11.",
|
||||
"PlanCode": "6190ba",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 15:21:57.6729366",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "b9768a1d91444d4a86b7a013467bee95",
|
||||
"PlanName": "8ulll",
|
||||
"PlanCode": "6899be",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 15:29:03.2029069",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "98621898cd514bc9a1ac0c92362284f4",
|
||||
"PlanName": "7u",
|
||||
"PlanCode": "7651fe",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 15:57:16.4898686",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "4d03142fd86844db8e23c19061b3d505",
|
||||
"PlanName": "55555",
|
||||
"PlanCode": "7963fe",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:23:37.7271107",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "c78c3f38a59748c3aef949405e434b05",
|
||||
"PlanName": "44443",
|
||||
"PlanCode": "4564dd",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:29:26.6765074",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "0fc4ffd86091451db26162af4f7b235e",
|
||||
"PlanName": "u",
|
||||
"PlanCode": "9246de",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:34:15.4217796",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "a08748982b934daab8752f55796e1b0c",
|
||||
"PlanName": "666y",
|
||||
"PlanCode": "5492ce",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:38:55.6092122",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "2317611bdb614e45b61a5118e58e3a2a",
|
||||
"PlanName": "8ull、",
|
||||
"PlanCode": "4641de",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:46:26.6184295",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "62cb45ac3af64a46aa6d450ba56963e7",
|
||||
"PlanName": "33333",
|
||||
"PlanCode": "1270aa",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:49:19.6115492",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "321f717a3a2640a3bfc9515aee7d1052",
|
||||
"PlanName": "999",
|
||||
"PlanCode": "7597ed",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-01-19 16:58:22.6149002",
|
||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
||||
},
|
||||
{
|
||||
"uuid": "6c3246ac0f974a6abc24c83bf45e1cf4",
|
||||
"PlanName": "QPCR",
|
||||
"PlanCode": "7297ad",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-02-19 13:03:44.3456134",
|
||||
"MatrixId": "f02830f3-ed67-49fb-9865-c31828ba3a48"
|
||||
},
|
||||
{
|
||||
"uuid": "1d307a2c095b461abeec6e8521565ad3",
|
||||
"PlanName": "绝对定量",
|
||||
"PlanCode": "8540af",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-02-19 13:35:14.2243691",
|
||||
"MatrixId": "739ddf78-e04c-4d43-9293-c35d31f36f51"
|
||||
},
|
||||
{
|
||||
"uuid": "bbd6dc765867466ca2a415525f5bdbdd",
|
||||
"PlanName": "血凝",
|
||||
"PlanCode": "6513ee",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-02-20 16:14:25.0364174",
|
||||
"MatrixId": "20e70dcb-63f6-4bac-82e3-29e88eb6a7ab"
|
||||
},
|
||||
{
|
||||
"uuid": "f7282ecbfee44e91b05cefbc1beac1ae",
|
||||
"PlanName": "血凝抑制",
|
||||
"PlanCode": "1431ba",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-02-21 10:00:05.8661038",
|
||||
"MatrixId": "1c948beb-4c32-494f-b226-14bb84b3e144"
|
||||
},
|
||||
{
|
||||
"uuid": "196e0d757c574020932b64b69e88fac9",
|
||||
"PlanName": "测试杀杀杀",
|
||||
"PlanCode": "9833df",
|
||||
"PlanTarget": "",
|
||||
"Annotate": "",
|
||||
"CreateName": "",
|
||||
"CreateDate": "2024-02-21 10:54:19.3136491",
|
||||
"MatrixId": "3667ead7-9044-46ad-b73e-655b57c8c6b9"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,302 @@
|
||||
[
|
||||
{
|
||||
"id": "630a9ca9-dfbf-40f9-b90b-6df73e6a1d7f",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "db955443-1397-4a7a-a0cc-185eb6422c27",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "635e8265-e2b9-430e-8a4e-ddf94256266f",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 2,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "6de1521d-a249-4a7e-800f-1d49b5c7b56f",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "4f9f2527-0f71-4ec4-a0ac-e546407e2960",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "55ecff40-453f-4a5f-9ed3-1267b0a03cae",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "7dcd9c87-6702-4659-b28a-f6565b27f8e3",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "67e51bd6-6eee-46e4-931c-73d9e07397eb",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "e1289406-4f5e-4966-a1e6-fb29be6cd4bd",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "4ecb9ef7-cbd4-44bc-a6a9-fdbbefdc01d6",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "c7bcaeeb-7ce7-479d-8dae-e82f4023a2b6",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "e502d5ee-3197-4f60-8ac4-3bc005349dfd",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "829c78b0-9e05-448f-9531-6d19c094c83f",
|
||||
"number": 8,
|
||||
"name": "T8",
|
||||
"row": 1,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "d0fd64d6-360d-4f5e-9451-21a332e247f5",
|
||||
"number": 9,
|
||||
"name": "T9",
|
||||
"row": 2,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "7f3da25d-0be0-4e07-885f-fbbbfa952f9f",
|
||||
"number": 10,
|
||||
"name": "T10",
|
||||
"row": 2,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "491d396d-7264-43d6-9ad4-60bffbe66c26",
|
||||
"number": 11,
|
||||
"name": "T11",
|
||||
"row": 2,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "a8853b6d-639d-46f9-a4bf-9153c0c22461",
|
||||
"number": 12,
|
||||
"name": "T12",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "b7beb8d0-0003-471d-bd8d-a9c0e09b07d5",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "306e3f96-a6d7-484a-83ef-722e3710d5c4",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "4e7bb617-ac1a-4360-b379-7ac4197089c4",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "af583180-c29d-418e-9061-9e030f77cf57",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 2,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "24a85ce8-e9e3-44f5-9d08-25116173ba75",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "7bf61a40-f65a-4d2f-bb19-d42bfd80e2e9",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "a3177806-3c02-4c4f-86d6-604a38c2ba2a",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "8ccaad5a-8588-4ff3-b0d7-17e7fd5ac6cc",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "93ae7707-b6b8-4bc4-8700-c500c3d7b165",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "3591a07b-4922-4882-996f-7bebee843be1",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "669fdba9-b20c-4bd2-8352-8fe5682e3e0c",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "8bf3333e-4a73-4e4c-959a-8ae44e1038a2",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "2837bf69-273a-4cbb-a74c-0af1b362f609",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"uuid": "9a3007baa748457b8d5162f5c5918553",
|
||||
"ArmCode": "SC10",
|
||||
"ArmName": "单道-10uL",
|
||||
"CmdCode": "SC10",
|
||||
"ChannelNum": 1,
|
||||
"Dosage": 10,
|
||||
"CreateName": "admin",
|
||||
"CreateTime": "2021-11-13 14:04:02.000",
|
||||
"UpdateName": "admin",
|
||||
"UpdateTime": "2021-11-13 14:04:12.000"
|
||||
},
|
||||
{
|
||||
"uuid": "8f57a4cc859d4c02bffbeeadcfb2b661",
|
||||
"ArmCode": "SC300",
|
||||
"ArmName": "单道-300uL",
|
||||
"CmdCode": "SC300",
|
||||
"ChannelNum": 1,
|
||||
"Dosage": 300,
|
||||
"CreateName": "admin",
|
||||
"CreateTime": "2021-11-11 11:11:11.000",
|
||||
"UpdateName": "admin",
|
||||
"UpdateTime": "2021-11-11 11:11:11.000"
|
||||
},
|
||||
{
|
||||
"uuid": "8fe0320823de49a99bfa5060ce1aaa28",
|
||||
"ArmCode": "SC1250",
|
||||
"ArmName": "单道-1250",
|
||||
"CmdCode": "SC1250",
|
||||
"ChannelNum": 1,
|
||||
"Dosage": 1250,
|
||||
"CreateName": "admin",
|
||||
"CreateTime": "2021-11-12 10:10:10.000",
|
||||
"UpdateName": "admin",
|
||||
"UpdateTime": "2021-11-12 11:11:11.000"
|
||||
},
|
||||
{
|
||||
"uuid": "88f22c5384e94dbbad60961d4d2b5e91",
|
||||
"ArmCode": "MC10",
|
||||
"ArmName": "八道-10uL",
|
||||
"CmdCode": "MC10",
|
||||
"ChannelNum": 8,
|
||||
"Dosage": 10,
|
||||
"CreateName": "admin",
|
||||
"CreateTime": "2021-11-12 10:10:10.000",
|
||||
"UpdateName": "admin",
|
||||
"UpdateTime": "2021-11-13 12:12:12.000"
|
||||
},
|
||||
{
|
||||
"uuid": "09206ff90e64466f90ce6a785a24bad8",
|
||||
"ArmCode": "MC300",
|
||||
"ArmName": "八道-300uL",
|
||||
"CmdCode": "MC300",
|
||||
"ChannelNum": 8,
|
||||
"Dosage": 300,
|
||||
"CreateName": "admin",
|
||||
"CreateTime": "2021-11-12 12:12:12.000",
|
||||
"UpdateName": "admin",
|
||||
"UpdateTime": "2021-11-12 10:10:10.000"
|
||||
},
|
||||
{
|
||||
"uuid": "5afcbd7d1d6749079d1c94f8c2e68f06",
|
||||
"ArmCode": "MC1250",
|
||||
"ArmName": "八道-1250uL",
|
||||
"CmdCode": "MC1250",
|
||||
"ChannelNum": 8,
|
||||
"Dosage": 1250,
|
||||
"CreateName": "admin",
|
||||
"CreateTime": "2021-11-12 12:12:10.000",
|
||||
"UpdateName": "admin",
|
||||
"UpdateTime": "2021-11-12 12:11:11.000"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"uuid": "bd52d6566534441ea523265814dc06e8",
|
||||
"uuidMaterial": "01bdeb95a1314dc78b8f25667b08d531",
|
||||
"ChannelNum": 8,
|
||||
"HoleNo": 96,
|
||||
"HoleCenterXYZ": "300",
|
||||
"uuidLayoutMaster": "4f35adc958c540fcb40d6f9dd51e40fa"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"uuid": "4f35adc958c540fcb40d6f9dd51e40fa",
|
||||
"BoardCode": 34,
|
||||
"BoardNum": 1,
|
||||
"BoardLength": 500,
|
||||
"BoardWidth": 400,
|
||||
"BoardColum": 4,
|
||||
"BoardRow": 3,
|
||||
"TotalColum": 4,
|
||||
"TotalRow": 3,
|
||||
"BoardCenterXY": "300",
|
||||
"HoleQty": 96,
|
||||
"Version": 1,
|
||||
"CreateTime": "2021-11-15",
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2021-11-15",
|
||||
"UpdateName": "admin"
|
||||
}
|
||||
]
|
||||
180578
unilabos/devices/liquid_handling/prcxi/json_output/base_plan_detail.json
Normal file
180578
unilabos/devices/liquid_handling/prcxi/json_output/base_plan_detail.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
||||
[
|
||||
{
|
||||
"id": "ef121889-2724-4b3d-a786-bbf0bd213c3d",
|
||||
"name": "9300_V02",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"create_name": "",
|
||||
"create_time": "2023-08-12 16:02:20.994",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "9300_V02",
|
||||
"isUse": 0
|
||||
},
|
||||
{
|
||||
"id": "9af15efc-29d2-4c44-8533-bbaf24913be6",
|
||||
"name": "9310",
|
||||
"row": 3,
|
||||
"col": 4,
|
||||
"create_name": "",
|
||||
"create_time": "2023-08-12 16:23:07.472",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "9310",
|
||||
"isUse": 0
|
||||
},
|
||||
{
|
||||
"id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546",
|
||||
"name": "6版位",
|
||||
"row": 2,
|
||||
"col": 4,
|
||||
"create_name": "",
|
||||
"create_time": "2023-10-09 11:05:57.244",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "6版位",
|
||||
"isUse": 0
|
||||
},
|
||||
{
|
||||
"id": "77673540-92c4-4404-b659-4257034a9c5e",
|
||||
"name": "9300_V03",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"create_name": "",
|
||||
"create_time": "2024-01-20 08:49:09.620",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "9300_V03",
|
||||
"isUse": 0
|
||||
},
|
||||
{
|
||||
"id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e",
|
||||
"name": "9320",
|
||||
"row": 4,
|
||||
"col": 7,
|
||||
"create_name": "",
|
||||
"create_time": "2025-03-10 13:44:17.994",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "9320",
|
||||
"isUse": 0
|
||||
},
|
||||
{
|
||||
"id": "54092457-a8b8-4457-bccd-e8c251e83ebd",
|
||||
"name": "7.17演示",
|
||||
"row": 4,
|
||||
"col": 4,
|
||||
"create_name": "",
|
||||
"create_time": "2025-07-12 17:08:38.336",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "7.17演示",
|
||||
"isUse": 0
|
||||
},
|
||||
{
|
||||
"id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc",
|
||||
"name": "北京大学 16版位",
|
||||
"row": 4,
|
||||
"col": 4,
|
||||
"create_name": "",
|
||||
"create_time": "2025-09-03 13:23:51.781",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "北京大学 16版位",
|
||||
"isUse": 1
|
||||
},
|
||||
{
|
||||
"id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a",
|
||||
"name": "TEST",
|
||||
"row": 4,
|
||||
"col": 4,
|
||||
"create_name": "",
|
||||
"create_time": "2025-10-27 14:36:03.266",
|
||||
"update_name": null,
|
||||
"update_time": null,
|
||||
"remark": "TEST",
|
||||
"isUse": 0
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,872 @@
|
||||
[
|
||||
{
|
||||
"id": "630a9ca9-dfbf-40f9-b90b-6df73e6a1d7f",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "db955443-1397-4a7a-a0cc-185eb6422c27",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "635e8265-e2b9-430e-8a4e-ddf94256266f",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 2,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "6de1521d-a249-4a7e-800f-1d49b5c7b56f",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "4f9f2527-0f71-4ec4-a0ac-e546407e2960",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
||||
},
|
||||
{
|
||||
"id": "55ecff40-453f-4a5f-9ed3-1267b0a03cae",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "7dcd9c87-6702-4659-b28a-f6565b27f8e3",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "67e51bd6-6eee-46e4-931c-73d9e07397eb",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "e1289406-4f5e-4966-a1e6-fb29be6cd4bd",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "4ecb9ef7-cbd4-44bc-a6a9-fdbbefdc01d6",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "c7bcaeeb-7ce7-479d-8dae-e82f4023a2b6",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "e502d5ee-3197-4f60-8ac4-3bc005349dfd",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "829c78b0-9e05-448f-9531-6d19c094c83f",
|
||||
"number": 8,
|
||||
"name": "T8",
|
||||
"row": 1,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "d0fd64d6-360d-4f5e-9451-21a332e247f5",
|
||||
"number": 9,
|
||||
"name": "T9",
|
||||
"row": 2,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "7f3da25d-0be0-4e07-885f-fbbbfa952f9f",
|
||||
"number": 10,
|
||||
"name": "T10",
|
||||
"row": 2,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "491d396d-7264-43d6-9ad4-60bffbe66c26",
|
||||
"number": 11,
|
||||
"name": "T11",
|
||||
"row": 2,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "a8853b6d-639d-46f9-a4bf-9153c0c22461",
|
||||
"number": 12,
|
||||
"name": "T12",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
||||
},
|
||||
{
|
||||
"id": "b7beb8d0-0003-471d-bd8d-a9c0e09b07d5",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "306e3f96-a6d7-484a-83ef-722e3710d5c4",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "4e7bb617-ac1a-4360-b379-7ac4197089c4",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "af583180-c29d-418e-9061-9e030f77cf57",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 2,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "24a85ce8-e9e3-44f5-9d08-25116173ba75",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "7bf61a40-f65a-4d2f-bb19-d42bfd80e2e9",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "a3177806-3c02-4c4f-86d6-604a38c2ba2a",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
||||
},
|
||||
{
|
||||
"id": "8ccaad5a-8588-4ff3-b0d7-17e7fd5ac6cc",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "93ae7707-b6b8-4bc4-8700-c500c3d7b165",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "3591a07b-4922-4882-996f-7bebee843be1",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "669fdba9-b20c-4bd2-8352-8fe5682e3e0c",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "8bf3333e-4a73-4e4c-959a-8ae44e1038a2",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "2837bf69-273a-4cbb-a74c-0af1b362f609",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
||||
},
|
||||
{
|
||||
"id": "e9d352fa-816a-4c01-a9e2-f52bce8771f1",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 4,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "713f1d85-b671-49f1-a2f9-11a64e5bb545",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 4,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "ba2d8fd6-e2fa-4dd3-8afc-13472ca12afb",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 4,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "68137a87-ae26-4e27-8953-4b1335ed957c",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "182b2814-9c89-4a75-8456-9a82e774f876",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 0,
|
||||
"col": 4,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "bc149d3c-9d54-45f0-8c33-23a5d4b70aff",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 0,
|
||||
"col": 5,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "7d9ce812-c39c-42fe-9b73-f35364a7b01f",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 0,
|
||||
"col": 6,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "4907b17d-c3f8-40a6-a8a2-e874f66195b1",
|
||||
"number": 8,
|
||||
"name": "T8",
|
||||
"row": 1,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "f858fdb5-649f-4cb2-8e95-06a1b2d97113",
|
||||
"number": 9,
|
||||
"name": "T9",
|
||||
"row": 1,
|
||||
"col": 4,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "cc5f91d2-494a-4991-9dda-3b82ae61556b",
|
||||
"number": 10,
|
||||
"name": "T10",
|
||||
"row": 1,
|
||||
"col": 5,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "afed9a1f-2f48-4ca9-ae14-eb1ae4e80181",
|
||||
"number": 11,
|
||||
"name": "T11",
|
||||
"row": 1,
|
||||
"col": 6,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "1d39cacd-7828-4318-9d4f-5bf8fc21d77d",
|
||||
"number": 12,
|
||||
"name": "T12",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "086912ac-4f33-4214-a2c8-22acb5291bfe",
|
||||
"number": 13,
|
||||
"name": "T13",
|
||||
"row": 2,
|
||||
"col": 4,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "89d43ea4-93f6-4cbf-aba4-564b0067295f",
|
||||
"number": 14,
|
||||
"name": "T14",
|
||||
"row": 2,
|
||||
"col": 5,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "866b12a8-5ef6-426d-a65b-b0583a3d8f16",
|
||||
"number": 15,
|
||||
"name": "T15",
|
||||
"row": 2,
|
||||
"col": 6,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "6c5969a9-e763-48f4-97f4-a9027e3ea7ef",
|
||||
"number": 16,
|
||||
"name": "T16",
|
||||
"row": 3,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "af8370be-076d-455d-b0b3-dd246f76d930",
|
||||
"number": 17,
|
||||
"name": "T17",
|
||||
"row": 3,
|
||||
"col": 4,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "abf2b8c7-79ef-4fd1-9f9b-14e7e6a128c7",
|
||||
"number": 18,
|
||||
"name": "T18",
|
||||
"row": 3,
|
||||
"col": 5,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "ca92a1e9-eb7d-4f9a-a42c-9bae461da797",
|
||||
"number": 19,
|
||||
"name": "T19",
|
||||
"row": 3,
|
||||
"col": 6,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
||||
},
|
||||
{
|
||||
"id": "4a4df4fd-ea0b-461c-aad4-032bfda5abab",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 4,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "dba90870-4b7a-4fbd-b33f-948bbb594703",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "fddc5c2b-157f-4554-8b39-2c9e338f4d3a",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "2569a396-2cd8-4cac-8b78-a8af1313c993",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "f0f693c7-a45f-4dd3-b629-621461ca9992",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "9dcba2bf-8a48-4bc6-a9b1-88f51ffaa8af",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "08449a38-0dca-48c4-a156-6f1055cf74c4",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "6ec7343f-12b9-42ae-86d1-3894758e69b4",
|
||||
"number": 8,
|
||||
"name": "T8",
|
||||
"row": 2,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "b5f02dbc-ffc6-452a-ad9f-2d1ff3db2064",
|
||||
"number": 9,
|
||||
"name": "T9",
|
||||
"row": 2,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "7635380a-4f96-4894-9a54-37c2bd27f148",
|
||||
"number": 10,
|
||||
"name": "T10",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "b4b6b063-5a0b-45a2-aa47-f427d4cd06f6",
|
||||
"number": 11,
|
||||
"name": "T11",
|
||||
"row": 3,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "af02c689-7bca-476b-bd05-ce21d3e83f27",
|
||||
"number": 12,
|
||||
"name": "T12",
|
||||
"row": 3,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "52a42e58-c0d6-420c-bc0b-575f749c7e3b",
|
||||
"number": 13,
|
||||
"name": "T13",
|
||||
"row": 3,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
||||
},
|
||||
{
|
||||
"id": "169c12fe-e2f4-465e-9fd3-e58eac83a502",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "b6072651-1df5-4946-a5b4-fbff3fa54e6a",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "d0b8ea7c-f06e-4d94-98a8-70ffcba73c47",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "a7a8eb69-63f6-494e-a441-b7aef0f7c8a4",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "21966669-6761-4e37-947c-12fec82173fb",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "2227b825-fe1d-4fa3-bcb2-6e4b3c10ea53",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "b799da88-c2d9-4ec4-81ec-bc0991a50fe5",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "adaaa00a-ff6b-4bd8-b8f1-bb100488f306",
|
||||
"number": 8,
|
||||
"name": "T8",
|
||||
"row": 1,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "3bc98311-b548-46d3-a0e0-4f1edcf10e24",
|
||||
"number": 9,
|
||||
"name": "T9",
|
||||
"row": 2,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "81befc70-d249-49af-93dd-2efbe88c0211",
|
||||
"number": 10,
|
||||
"name": "T10",
|
||||
"row": 2,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "45dd5535-0293-4d27-beab-1e486657b148",
|
||||
"number": 11,
|
||||
"name": "T11",
|
||||
"row": 2,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "12ccf33a-6fe7-44a4-8643-b0b0ac6dd181",
|
||||
"number": 12,
|
||||
"name": "T12",
|
||||
"row": 2,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "900272dd-23fd-41a4-a366-254999a30487",
|
||||
"number": 13,
|
||||
"name": "T13",
|
||||
"row": 3,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "c366710d-2b81-4cee-8667-2b86e77e5c34",
|
||||
"number": 14,
|
||||
"name": "T14",
|
||||
"row": 3,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "e18a9271-bc66-4c2b-8bc1-0fb129b5cc2f",
|
||||
"number": 15,
|
||||
"name": "T15",
|
||||
"row": 3,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "6737cba0-de84-4c1f-992d-645e7f159b0c",
|
||||
"number": 16,
|
||||
"name": "T16",
|
||||
"row": 3,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
||||
},
|
||||
{
|
||||
"id": "8ace38ab-dbc7-48a1-8226-0fe92d176e07",
|
||||
"number": 1,
|
||||
"name": "T1",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "033fec53-c52d-4b59-aec6-2135ae0e18b9",
|
||||
"number": 2,
|
||||
"name": "T2",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "fa730930-8709-4250-928f-f757fce57b60",
|
||||
"number": 3,
|
||||
"name": "T3",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "e279d6f1-5243-4224-8953-1033dbea25ac",
|
||||
"number": 4,
|
||||
"name": "T4",
|
||||
"row": 0,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "76bd9426-6324-4af2-b12f-6ec0ff8c416e",
|
||||
"number": 5,
|
||||
"name": "T5",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "3f4ff652-3d87-4254-a235-bafde3359dae",
|
||||
"number": 6,
|
||||
"name": "T6",
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "a38e94af-e91e-4e7a-b49d-8668001bb356",
|
||||
"number": 7,
|
||||
"name": "T7",
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "9e45da24-1346-4886-a303-932880a79954",
|
||||
"number": 8,
|
||||
"name": "T8",
|
||||
"row": 1,
|
||||
"col": 3,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
},
|
||||
{
|
||||
"id": "1ac46e58-86ae-42d9-b230-d476b984507a",
|
||||
"number": 9,
|
||||
"name": "T9",
|
||||
"row": 2,
|
||||
"col": 0,
|
||||
"row_span": 1,
|
||||
"col_span": 1,
|
||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,58 @@
|
||||
[
|
||||
{
|
||||
"uuid": "4034fa042e7f418db42ab80b0044a8cd",
|
||||
"Code": "MDHC-001-10",
|
||||
"Key": "c28ae2cb",
|
||||
"Value": "MDHC-001-1000522001001612db9dc",
|
||||
"CreateTime": "2022-01-22 17:07:00.8651386"
|
||||
},
|
||||
{
|
||||
"uuid": "8fb6d7589fdd42df93c1e1989ff13a62",
|
||||
"Code": "MDHC-001-10",
|
||||
"Key": "52980979",
|
||||
"Value": "MDHC-001-100052200100119bb6731",
|
||||
"CreateTime": "2022-01-22 20:19:20.9444209"
|
||||
},
|
||||
{
|
||||
"uuid": "efc4c92b40a94de6b0662c64486c18d1",
|
||||
"Code": "MDHC-001-10",
|
||||
"Key": "79da8402",
|
||||
"Value": "MDHC-001-1000522001001e24ea780",
|
||||
"CreateTime": "2022-01-22 20:19:26.8107506"
|
||||
},
|
||||
{
|
||||
"uuid": "3b81b1a9eabc4449b4dcbbbde47cb17f",
|
||||
"Code": "MDHC-001-10",
|
||||
"Key": "daa51755",
|
||||
"Value": "MDHC-001-100052200100185dd22e2",
|
||||
"CreateTime": "2022-01-22 20:19:36.1581374"
|
||||
},
|
||||
{
|
||||
"uuid": "d005a70801544e42ab9d216ad68dbf50",
|
||||
"Code": "MDHC-023-0.2",
|
||||
"Key": "992bbdab",
|
||||
"Value": "MDHC-023-0.2005220010014871a385",
|
||||
"CreateTime": "2022-02-16 15:49:53.760377"
|
||||
},
|
||||
{
|
||||
"uuid": "222315afb8e04320b0fcff10e3ddb8ae",
|
||||
"Code": "MDHC-023-0.2",
|
||||
"Key": "76d23270",
|
||||
"Value": "MDHC-023-0.200522001001e61547ee",
|
||||
"CreateTime": "2022-02-16 15:50:05.1932055"
|
||||
},
|
||||
{
|
||||
"uuid": "31e2a5d4f884419aa9ba96cef98b7385",
|
||||
"Code": "MDHC-023-0.2",
|
||||
"Key": "ba2b8a46",
|
||||
"Value": "MDHC-023-0.2005220010013bfed6cf",
|
||||
"CreateTime": "2022-02-16 17:26:20.0024235"
|
||||
},
|
||||
{
|
||||
"uuid": "9ccb8e0c5ca64ef09b8aced680395335",
|
||||
"Code": "MDHC-023-0.2",
|
||||
"Key": "1d1276d0",
|
||||
"Value": "MDHC-023-0.2005220010015c039a9c",
|
||||
"CreateTime": "2022-02-16 17:26:31.8479966"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"uuid": "f3932aeae93533f19c0519c4c14702aa",
|
||||
"RoleCode": "admin",
|
||||
"RoleName": "管理员",
|
||||
"RoleMenu": "all",
|
||||
"CreateTime": "2022-02-26 00:00:00.000",
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2022-02-26 14:50:10.000",
|
||||
"UpdateName": "admin"
|
||||
},
|
||||
{
|
||||
"uuid": "8c822592b360345fb59690e49ac6b181",
|
||||
"RoleCode": "user",
|
||||
"RoleName": "实验员",
|
||||
"RoleMenu": "nosetting",
|
||||
"CreateTime": "2022-02-26 14:54:16.000",
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2022-02-26 14:54:19.000",
|
||||
"UpdateName": "admin"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
[
|
||||
{
|
||||
"uuid": "f3932aeae93533f19c0519c4c14702dd",
|
||||
"UserName": "admin",
|
||||
"Password": "NuGlByx4NZBm7XcV9f89qA==",
|
||||
"RealName": "管理员",
|
||||
"IsEnable": 1,
|
||||
"uuidRole": "f3932aeae93533f19c0519c4c14702aa",
|
||||
"IsDel": 0,
|
||||
"CreateTime": "2022-02-26 14:51:41.000",
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2022-02-26 14:51:49.000",
|
||||
"UpdateName": "admin"
|
||||
},
|
||||
{
|
||||
"uuid": "5c522592b366645fb55690e49ac6b166",
|
||||
"UserName": "user",
|
||||
"Password": "4QrcOUm6Wau+VuBX8g+IPg==",
|
||||
"RealName": "实验员",
|
||||
"IsEnable": 1,
|
||||
"uuidRole": "8c822592b360345fb59690e49ac6b181",
|
||||
"IsDel": 0,
|
||||
"CreateTime": "2022-02-26 14:56:57.000",
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2022-02-26 14:58:39.000",
|
||||
"UpdateName": "admin"
|
||||
},
|
||||
{
|
||||
"uuid": "ju0514zjhi9267mz8s0buspq8b9s0bgb",
|
||||
"UserName": "Administrator",
|
||||
"Password": "3J17Il4KOR+wKPszf/0cHQ==",
|
||||
"RealName": "超级管理员",
|
||||
"IsEnable": 1,
|
||||
"uuidRole": "f3932aeae93533f19c0519c4c14702aa",
|
||||
"IsDel": 0,
|
||||
"CreateTime": "2023-08-12 00:00:00.000",
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2023-08-12 00:00:00.000",
|
||||
"UpdateName": "admin"
|
||||
},
|
||||
{
|
||||
"uuid": "2",
|
||||
"UserName": "shortcut",
|
||||
"Password": "4QrcOUm6Wau+VuBX8g+IPg==",
|
||||
"RealName": "实验员",
|
||||
"IsEnable": 1,
|
||||
"uuidRole": "8c822592b360345fb59690e49ac6b181",
|
||||
"IsDel": 0,
|
||||
"CreateTime": null,
|
||||
"CreateName": "admin",
|
||||
"UpdateTime": "2023-10-23 00:00:00.000",
|
||||
"UpdateName": null
|
||||
}
|
||||
]
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
from typing import Any, List, Dict, Optional, Tuple, TypedDict, Union, Sequence, Iterator, Literal
|
||||
from typing import Any, List, Dict, Optional, OrderedDict, Tuple, TypedDict, Union, Sequence, Iterator, Literal
|
||||
|
||||
from pylabrobot.liquid_handling import (
|
||||
LiquidHandlerBackend,
|
||||
@@ -28,7 +28,7 @@ from pylabrobot.liquid_handling.standard import (
|
||||
ResourceMove,
|
||||
ResourceDrop,
|
||||
)
|
||||
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash
|
||||
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash, TubeRack, PlateAdapter
|
||||
|
||||
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
@@ -70,50 +70,129 @@ class PRCXI9300Deck(Deck):
|
||||
super().__init__(name, size_x, size_y, size_z)
|
||||
self.slots = [None] * 6 # PRCXI 9300 有 6 个槽位
|
||||
|
||||
|
||||
class PRCXI9300Container(Plate, TipRack):
|
||||
"""PRCXI 9300 的专用 Container 类,继承自 Plate和TipRack。
|
||||
|
||||
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
||||
class PRCXI9300Plate(Plate):
|
||||
"""
|
||||
专用孔板类:
|
||||
1. 继承自 PLR 原生 Plate,保留所有物理特性。
|
||||
2. 增加 material_info 参数,用于在初始化时直接绑定 Unilab UUID。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
category: str,
|
||||
ordering: collections.OrderedDict,
|
||||
model: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
|
||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
||||
category: str = "plate",
|
||||
ordered_items: collections.OrderedDict = None,
|
||||
ordering: Optional[collections.OrderedDict] = None,
|
||||
model: Optional[str] = None,
|
||||
material_info: Optional[Dict[str, Any]] = None,
|
||||
**kwargs):
|
||||
items = ordered_items if ordered_items is not None else ordering
|
||||
super().__init__(name, size_x, size_y, size_z,
|
||||
ordered_items=items,
|
||||
category=category,
|
||||
model=model, **kwargs)
|
||||
|
||||
self._unilabos_state = {}
|
||||
if material_info:
|
||||
self._unilabos_state["Material"] = material_info
|
||||
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""从给定的状态加载工作台信息。"""
|
||||
super().load_state(state)
|
||||
self._unilabos_state = state
|
||||
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
try:
|
||||
data = super().serialize_state()
|
||||
except AttributeError:
|
||||
data = {}
|
||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
||||
safe_state = {}
|
||||
for k, v in self._unilabos_state.items():
|
||||
# 如果是 Material 字典,深入检查
|
||||
if k == "Material" and isinstance(v, dict):
|
||||
safe_material = {}
|
||||
for mk, mv in v.items():
|
||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_material[mk] = mv
|
||||
else:
|
||||
# 打印日志提醒(可选)
|
||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
||||
pass
|
||||
safe_state[k] = safe_material
|
||||
# 其他顶层属性也进行类型检查
|
||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_state[k] = v
|
||||
|
||||
data.update(safe_state)
|
||||
return data
|
||||
|
||||
class PRCXI9300TipRack(TipRack):
|
||||
""" 专用吸头盒类 """
|
||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
||||
category: str = "tip_rack",
|
||||
ordered_items: collections.OrderedDict = None,
|
||||
ordering: Optional[collections.OrderedDict] = None,
|
||||
model: Optional[str] = None,
|
||||
material_info: Optional[Dict[str, Any]] = None,
|
||||
**kwargs):
|
||||
items = ordered_items if ordered_items is not None else ordering
|
||||
super().__init__(name, size_x, size_y, size_z,
|
||||
ordered_items=items,
|
||||
category=category,
|
||||
model=model, **kwargs)
|
||||
self._unilabos_state = {}
|
||||
if material_info:
|
||||
self._unilabos_state["Material"] = material_info
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
super().load_state(state)
|
||||
self._unilabos_state = state
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
data = super().serialize_state()
|
||||
data.update(self._unilabos_state)
|
||||
try:
|
||||
data = super().serialize_state()
|
||||
except AttributeError:
|
||||
data = {}
|
||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
||||
safe_state = {}
|
||||
for k, v in self._unilabos_state.items():
|
||||
# 如果是 Material 字典,深入检查
|
||||
if k == "Material" and isinstance(v, dict):
|
||||
safe_material = {}
|
||||
for mk, mv in v.items():
|
||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_material[mk] = mv
|
||||
else:
|
||||
# 打印日志提醒(可选)
|
||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
||||
pass
|
||||
safe_state[k] = safe_material
|
||||
# 其他顶层属性也进行类型检查
|
||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_state[k] = v
|
||||
|
||||
data.update(safe_state)
|
||||
return data
|
||||
|
||||
|
||||
|
||||
class PRCXI9300Trash(Trash):
|
||||
"""PRCXI 9300 的专用 Trash 类,继承自 Trash。
|
||||
|
||||
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float, category: str, **kwargs):
|
||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
||||
category: str = "trash",
|
||||
material_info: Optional[Dict[str, Any]] = None,
|
||||
**kwargs):
|
||||
|
||||
if name != "trash":
|
||||
name = "trash"
|
||||
print("PRCXI9300Trash name must be 'trash', using 'trash' instead.")
|
||||
super().__init__(name, size_x, size_y, size_z, category=category, **kwargs)
|
||||
print(f"Warning: PRCXI9300Trash usually expects name='trash' for backend logic, but got '{name}'.")
|
||||
super().__init__(name, size_x, size_y, size_z, **kwargs)
|
||||
self._unilabos_state = {}
|
||||
# 初始化时注入 UUID
|
||||
if material_info:
|
||||
self._unilabos_state["Material"] = material_info
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""从给定的状态加载工作台信息。"""
|
||||
@@ -121,10 +200,152 @@ class PRCXI9300Trash(Trash):
|
||||
self._unilabos_state = state
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
data = super().serialize_state()
|
||||
data.update(self._unilabos_state)
|
||||
try:
|
||||
data = super().serialize_state()
|
||||
except AttributeError:
|
||||
data = {}
|
||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
||||
safe_state = {}
|
||||
for k, v in self._unilabos_state.items():
|
||||
# 如果是 Material 字典,深入检查
|
||||
if k == "Material" and isinstance(v, dict):
|
||||
safe_material = {}
|
||||
for mk, mv in v.items():
|
||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_material[mk] = mv
|
||||
else:
|
||||
# 打印日志提醒(可选)
|
||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
||||
pass
|
||||
safe_state[k] = safe_material
|
||||
# 其他顶层属性也进行类型检查
|
||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_state[k] = v
|
||||
|
||||
data.update(safe_state)
|
||||
return data
|
||||
|
||||
class PRCXI9300TubeRack(TubeRack):
|
||||
"""
|
||||
专用管架类:用于 EP 管架、试管架等。
|
||||
继承自 PLR 的 TubeRack,并支持注入 material_info (UUID)。
|
||||
"""
|
||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
||||
category: str = "tube_rack",
|
||||
items: Optional[Dict[str, Any]] = None,
|
||||
ordered_items: Optional[OrderedDict] = None,
|
||||
model: Optional[str] = None,
|
||||
material_info: Optional[Dict[str, Any]] = None,
|
||||
**kwargs):
|
||||
|
||||
# 兼容处理:PLR 的 TubeRack 构造函数可能接受 items 或 ordered_items
|
||||
items_to_pass = items if items is not None else ordered_items
|
||||
super().__init__(name, size_x, size_y, size_z,
|
||||
ordered_items=ordered_items,
|
||||
model=model,
|
||||
**kwargs)
|
||||
|
||||
self._unilabos_state = {}
|
||||
if material_info:
|
||||
self._unilabos_state["Material"] = material_info
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
try:
|
||||
data = super().serialize_state()
|
||||
except AttributeError:
|
||||
data = {}
|
||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
||||
safe_state = {}
|
||||
for k, v in self._unilabos_state.items():
|
||||
# 如果是 Material 字典,深入检查
|
||||
if k == "Material" and isinstance(v, dict):
|
||||
safe_material = {}
|
||||
for mk, mv in v.items():
|
||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_material[mk] = mv
|
||||
else:
|
||||
# 打印日志提醒(可选)
|
||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
||||
pass
|
||||
safe_state[k] = safe_material
|
||||
# 其他顶层属性也进行类型检查
|
||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_state[k] = v
|
||||
|
||||
data.update(safe_state)
|
||||
return data
|
||||
|
||||
class PRCXI9300PlateAdapter(PlateAdapter):
|
||||
"""
|
||||
专用板式适配器类:用于承载 Plate 的底座(如 PCR 适配器、磁吸架等)。
|
||||
支持注入 material_info (UUID)。
|
||||
"""
|
||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
||||
category: str = "plate_adapter",
|
||||
model: Optional[str] = None,
|
||||
material_info: Optional[Dict[str, Any]] = None,
|
||||
# 参数给予默认值 (标准96孔板尺寸)
|
||||
adapter_hole_size_x: float = 127.76,
|
||||
adapter_hole_size_y: float = 85.48,
|
||||
adapter_hole_size_z: float = 10.0, # 假设凹槽深度或板子放置高度
|
||||
dx: Optional[float] = None,
|
||||
dy: Optional[float] = None,
|
||||
dz: float = 0.0, # 默认Z轴偏移
|
||||
**kwargs):
|
||||
|
||||
# 自动居中计算:如果未指定 dx/dy,则根据适配器尺寸和孔尺寸计算居中位置
|
||||
if dx is None:
|
||||
dx = (size_x - adapter_hole_size_x) / 2
|
||||
if dy is None:
|
||||
dy = (size_y - adapter_hole_size_y) / 2
|
||||
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
dx=dx,
|
||||
dy=dy,
|
||||
dz=dz,
|
||||
adapter_hole_size_x=adapter_hole_size_x,
|
||||
adapter_hole_size_y=adapter_hole_size_y,
|
||||
adapter_hole_size_z=adapter_hole_size_z,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self._unilabos_state = {}
|
||||
if material_info:
|
||||
self._unilabos_state["Material"] = material_info
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
try:
|
||||
data = super().serialize_state()
|
||||
except AttributeError:
|
||||
data = {}
|
||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
||||
safe_state = {}
|
||||
for k, v in self._unilabos_state.items():
|
||||
# 如果是 Material 字典,深入检查
|
||||
if k == "Material" and isinstance(v, dict):
|
||||
safe_material = {}
|
||||
for mk, mv in v.items():
|
||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_material[mk] = mv
|
||||
else:
|
||||
# 打印日志提醒(可选)
|
||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
||||
pass
|
||||
safe_state[k] = safe_material
|
||||
# 其他顶层属性也进行类型检查
|
||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
||||
safe_state[k] = v
|
||||
|
||||
data.update(safe_state)
|
||||
return data
|
||||
|
||||
class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||
support_touch_tip = False
|
||||
@@ -154,10 +375,15 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||
tablets_info = []
|
||||
count = 0
|
||||
for child in deck.children:
|
||||
if "Material" in child._unilabos_state:
|
||||
child_state = getattr(child, "_unilabos_state", {})
|
||||
if "Material" in child_state:
|
||||
count += 1
|
||||
tablets_info.append(
|
||||
WorkTablets(Number=count, Code=f"T{count}", Material=child._unilabos_state["Material"])
|
||||
WorkTablets(
|
||||
Number=count,
|
||||
Code=f"T{count}",
|
||||
Material=child_state["Material"]
|
||||
)
|
||||
)
|
||||
if is_9320:
|
||||
print("当前设备是9320")
|
||||
@@ -434,7 +660,6 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||
async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
|
||||
return await super().move_to(well, dis_to_top, channel)
|
||||
|
||||
|
||||
class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
"""PRCXI 9300 的后端实现,继承自 LiquidHandlerBackend。
|
||||
|
||||
@@ -1533,31 +1758,31 @@ if __name__ == "__main__":
|
||||
from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul, opentrons_96_tiprack_10ul
|
||||
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||
|
||||
def get_well_container(name: str) -> PRCXI9300Container:
|
||||
def get_well_container(name: str) -> PRCXI9300Plate:
|
||||
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||
plate = PRCXI9300Container(
|
||||
name=name, size_x=50, size_y=50, size_z=10, category="plate", ordering=well_containers["ordering"]
|
||||
plate = PRCXI9300Plate(
|
||||
name=name, size_x=50, size_y=50, size_z=10, category="plate", ordered_items=well_containers["ordering"]
|
||||
)
|
||||
plate_serialized = plate.serialize()
|
||||
plate_serialized["parent_name"] = deck.name
|
||||
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||
new_plate: PRCXI9300Plate = PRCXI9300Plate.deserialize(well_containers)
|
||||
return new_plate
|
||||
|
||||
def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300Container:
|
||||
def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300TipRack:
|
||||
tip_racks = opentrons_96_tiprack_10ul(name).serialize()
|
||||
tip_rack = PRCXI9300Container(
|
||||
tip_rack = PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="tip_rack",
|
||||
ordering=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}),
|
||||
ordered_items=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}),
|
||||
)
|
||||
tip_rack_serialized = tip_rack.serialize()
|
||||
tip_rack_serialized["parent_name"] = deck.name
|
||||
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||
new_tip_rack: PRCXI9300TipRack = PRCXI9300TipRack.deserialize(tip_racks)
|
||||
return new_tip_rack
|
||||
|
||||
plate1 = get_tip_rack("RackT1")
|
||||
@@ -1604,8 +1829,8 @@ if __name__ == "__main__":
|
||||
}
|
||||
}
|
||||
)
|
||||
plate7 = PRCXI9300Container(
|
||||
name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()
|
||||
plate7 = PRCXI9300Plate(
|
||||
name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordered_items=collections.OrderedDict()
|
||||
)
|
||||
plate7.load_state({"Material": {"uuid": "04211a2dc93547fe9bf6121eac533650"}})
|
||||
plate8 = get_tip_rack("PlateT8")
|
||||
@@ -1679,13 +1904,13 @@ if __name__ == "__main__":
|
||||
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(
|
||||
PRCXI9300Container(
|
||||
PRCXI9300Plate(
|
||||
name="container_for_nothin3",
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="plate",
|
||||
ordering=collections.OrderedDict(),
|
||||
ordered_items=collections.OrderedDict(),
|
||||
),
|
||||
location=Coordinate(0, 0, 0),
|
||||
)
|
||||
@@ -1693,48 +1918,48 @@ if __name__ == "__main__":
|
||||
deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(
|
||||
PRCXI9300Container(
|
||||
PRCXI9300Plate(
|
||||
name="container_for_nothing7",
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="plate",
|
||||
ordering=collections.OrderedDict(),
|
||||
ordered_items=collections.OrderedDict(),
|
||||
),
|
||||
location=Coordinate(0, 0, 0),
|
||||
)
|
||||
deck.assign_child_resource(
|
||||
PRCXI9300Container(
|
||||
PRCXI9300Plate(
|
||||
name="container_for_nothing8",
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="plate",
|
||||
ordering=collections.OrderedDict(),
|
||||
ordered_items=collections.OrderedDict(),
|
||||
),
|
||||
location=Coordinate(0, 0, 0),
|
||||
)
|
||||
deck.assign_child_resource(plate9, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate10, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(
|
||||
PRCXI9300Container(
|
||||
PRCXI9300Plate(
|
||||
name="container_for_nothing11",
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="plate",
|
||||
ordering=collections.OrderedDict(),
|
||||
ordered_items=collections.OrderedDict(),
|
||||
),
|
||||
location=Coordinate(0, 0, 0),
|
||||
)
|
||||
deck.assign_child_resource(
|
||||
PRCXI9300Container(
|
||||
PRCXI9300Plate(
|
||||
name="container_for_nothing12",
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="plate",
|
||||
ordering=collections.OrderedDict(),
|
||||
ordered_items=collections.OrderedDict(),
|
||||
),
|
||||
location=Coordinate(0, 0, 0),
|
||||
)
|
||||
|
||||
841
unilabos/devices/liquid_handling/prcxi/prcxi_labware.py
Normal file
841
unilabos/devices/liquid_handling/prcxi/prcxi_labware.py
Normal file
@@ -0,0 +1,841 @@
|
||||
from typing import Optional
|
||||
from pylabrobot.resources import Tube, Coordinate
|
||||
from pylabrobot.resources.well import Well, WellBottomType, CrossSectionType
|
||||
from pylabrobot.resources.tip import Tip, TipCreator
|
||||
from pylabrobot.resources.tip_rack import TipRack, TipSpot
|
||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
||||
from pylabrobot.resources.height_volume_functions import (
|
||||
compute_height_from_volume_rectangle,
|
||||
compute_volume_from_height_rectangle,
|
||||
)
|
||||
|
||||
from .prcxi import PRCXI9300Plate, PRCXI9300TipRack, PRCXI9300Trash, PRCXI9300TubeRack, PRCXI9300PlateAdapter
|
||||
|
||||
def _make_tip_helper(volume: float, length: float, depth: float) -> Tip:
|
||||
"""
|
||||
PLR 的 Tip 类参数名为: maximal_volume, total_tip_length, fitting_depth
|
||||
"""
|
||||
return Tip(
|
||||
has_filter=False, # 默认无滤芯
|
||||
maximal_volume=volume,
|
||||
total_tip_length=length,
|
||||
fitting_depth=depth
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# 标准品 参照 PLR 标准库的参数,但是用 PRCXI9300Plate 实例化,并注入 UUID
|
||||
# =========================================================================
|
||||
def PRCXI_BioER_96_wellplate(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: ZX-019-2.2 (2.2ml 深孔板)
|
||||
原型: pylabrobot.resources.bioer.BioER_96_wellplate_Vb_2200uL
|
||||
"""
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.1,
|
||||
size_y=85.0,
|
||||
size_z=44.2,
|
||||
lid=None,
|
||||
model="PRCXI_BioER_96_wellplate",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "2.2ml 深孔板",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
size_x=8.25,
|
||||
size_y=8.25,
|
||||
size_z=39.3, # 修改过
|
||||
dx=9.5,
|
||||
dy=7.5,
|
||||
dz=6,
|
||||
material_z_thickness=0.8,
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
cross_section_type=CrossSectionType.RECTANGLE,
|
||||
bottom_type=WellBottomType.V, # 是否需要修改?
|
||||
max_volume=2200,
|
||||
),
|
||||
)
|
||||
def PRCXI_nest_1_troughplate(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: ZX-58-10000 (储液槽)
|
||||
原型: pylabrobot.resources.nest.nest_1_troughplate_195000uL_Vb
|
||||
"""
|
||||
well_size_x = 127.76 - (14.38 - 9 / 2) * 2
|
||||
well_size_y = 85.48 - (11.24 - 9 / 2) * 2
|
||||
well_kwargs = {
|
||||
"size_x": well_size_x,
|
||||
"size_y": well_size_y,
|
||||
"size_z": 26.85,
|
||||
"bottom_type": WellBottomType.V,
|
||||
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
|
||||
liquid_volume=liquid_volume, well_length=well_size_x, well_width=well_size_y
|
||||
),
|
||||
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
|
||||
liquid_height=liquid_height, well_length=well_size_x, well_width=well_size_y
|
||||
),
|
||||
"material_z_thickness": 31.4 - 26.85 - 3.55,
|
||||
}
|
||||
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.76,
|
||||
size_y=85.48,
|
||||
size_z=31.4,
|
||||
lid=None,
|
||||
model="PRCXI_Nest_1_troughplate",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
"Code": "ZX-58-10000",
|
||||
"Name": "储液槽",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=1,
|
||||
num_items_y=1,
|
||||
dx=14.38 - 9 / 2,
|
||||
dy=11.24 - 9 / 2,
|
||||
dz=3.55,
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
**well_kwargs, # 传入上面计算好的孔参数
|
||||
),
|
||||
)
|
||||
def PRCXI_BioRad_384_wellplate(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: q3 (384板)
|
||||
原型: pylabrobot.resources.biorad.BioRad_384_wellplate_50uL_Vb
|
||||
"""
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
# 直接抄录 PLR 标准品的物理尺寸
|
||||
size_x=127.76,
|
||||
size_y=85.48,
|
||||
size_z=10.40,
|
||||
model="BioRad_384_wellplate_50uL_Vb",
|
||||
category="plate",
|
||||
# 2. 注入 Unilab 必须的 UUID 信息
|
||||
material_info={
|
||||
"uuid": "853dcfb6226f476e8b23c250217dc7da",
|
||||
"Code": "q3",
|
||||
"Name": "384板",
|
||||
"SupplyType": 1,
|
||||
},
|
||||
# 3. 定义孔的排列 (抄录标准参数)
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=24,
|
||||
num_items_y=16,
|
||||
dx=10.58, # A1 左边缘距离板子左边缘 需要进一步测量
|
||||
dy=7.44, # P1 下边缘距离板子下边缘 需要进一步测量
|
||||
dz=1.05,
|
||||
item_dx=4.5,
|
||||
item_dy=4.5,
|
||||
size_x=3.10,
|
||||
size_y=3.10,
|
||||
size_z=9.35,
|
||||
max_volume=50,
|
||||
material_z_thickness=1,
|
||||
bottom_type=WellBottomType.V,
|
||||
cross_section_type=CrossSectionType.CIRCLE,
|
||||
)
|
||||
)
|
||||
def PRCXI_AGenBio_4_troughplate(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: sdfrth654 (4道储液槽)
|
||||
原型: pylabrobot.resources.agenbio.AGenBio_4_troughplate_75000uL_Vb
|
||||
"""
|
||||
INNER_WELL_WIDTH = 26.1
|
||||
INNER_WELL_LENGTH = 71.2
|
||||
well_kwargs = {
|
||||
"size_x": 26,
|
||||
"size_y": 71.2,
|
||||
"size_z": 42.55,
|
||||
"bottom_type": WellBottomType.FLAT,
|
||||
"cross_section_type": CrossSectionType.RECTANGLE,
|
||||
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
|
||||
liquid_volume,
|
||||
INNER_WELL_LENGTH,
|
||||
INNER_WELL_WIDTH,
|
||||
),
|
||||
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
|
||||
liquid_height,
|
||||
INNER_WELL_LENGTH,
|
||||
INNER_WELL_WIDTH,
|
||||
),
|
||||
"material_z_thickness": 1,
|
||||
}
|
||||
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.76,
|
||||
size_y=85.48,
|
||||
size_z=43.80,
|
||||
model="PRCXI_AGenBio_4_troughplate",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
|
||||
"Code": "sdfrth654",
|
||||
"Name": "4道储液槽",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=4,
|
||||
num_items_y=1,
|
||||
dx=9.8,
|
||||
dy=7.2,
|
||||
dz=0.9,
|
||||
item_dx=INNER_WELL_WIDTH + 1, # 1 mm wall thickness
|
||||
item_dy=INNER_WELL_LENGTH,
|
||||
**well_kwargs,
|
||||
),
|
||||
)
|
||||
def PRCXI_nest_12_troughplate(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: 12道储液槽 (12道储液槽)
|
||||
原型: pylabrobot.resources.nest.nest_12_troughplate_15000uL_Vb
|
||||
"""
|
||||
well_size_x = 8.2
|
||||
well_size_y = 71.2
|
||||
well_kwargs = {
|
||||
"size_x": well_size_x,
|
||||
"size_y": well_size_y,
|
||||
"size_z": 26.85,
|
||||
"bottom_type": WellBottomType.V,
|
||||
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
|
||||
liquid_volume=liquid_volume, well_length=well_size_x, well_width=well_size_y
|
||||
),
|
||||
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
|
||||
liquid_height=liquid_height, well_length=well_size_x, well_width=well_size_y
|
||||
),
|
||||
"material_z_thickness": 31.4 - 26.85 - 3.55,
|
||||
}
|
||||
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.76,
|
||||
size_y=85.48,
|
||||
size_z=31.4,
|
||||
lid=None,
|
||||
model="PRCXI_nest_12_troughplate",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "0f1639987b154e1fac78f4fb29a1f7c1",
|
||||
"Code": "12道储液槽",
|
||||
"Name": "12道储液槽",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=12,
|
||||
num_items_y=1,
|
||||
dx=14.38 - 8.2 / 2,
|
||||
dy=(85.48 - 71.2) / 2,
|
||||
dz=3.55,
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
**well_kwargs,
|
||||
),
|
||||
)
|
||||
def PRCXI_CellTreat_96_wellplate(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: ZX-78-096 (细菌培养皿)
|
||||
原型: pylabrobot.resources.celltreat.CellTreat_96_wellplate_350ul_Fb
|
||||
"""
|
||||
well_kwargs = {
|
||||
"size_x": 6.96,
|
||||
"size_y": 6.96,
|
||||
"size_z": 10.04,
|
||||
"bottom_type": WellBottomType.FLAT,
|
||||
"material_z_thickness": 1.75,
|
||||
"cross_section_type": CrossSectionType.CIRCLE,
|
||||
"max_volume": 300,
|
||||
}
|
||||
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.61,
|
||||
size_y=85.24,
|
||||
size_z=14.30,
|
||||
lid=None,
|
||||
model="PRCXI_CellTreat_96_wellplate",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f",
|
||||
"Code": "ZX-78-096",
|
||||
"Name": "细菌培养皿",
|
||||
"materialEnum": 4,
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=10.83,
|
||||
dy=7.67,
|
||||
dz=4.05,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
**well_kwargs,
|
||||
),
|
||||
)
|
||||
# =========================================================================
|
||||
# 自定义/需测量品 (Custom Measurement)
|
||||
# =========================================================================
|
||||
def PRCXI_10ul_eTips(name: str) -> PRCXI9300TipRack:
|
||||
"""
|
||||
对应 JSON Code: ZX-001-10+
|
||||
"""
|
||||
return PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=122.11,
|
||||
size_y=85.48, #修改
|
||||
size_z=58.23,
|
||||
model="PRCXI_10ul_eTips",
|
||||
material_info={
|
||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
||||
"Code": "ZX-001-10+",
|
||||
"Name": "10μL加长 Tip头",
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=7.97, #需要修改
|
||||
dy=5.0, #需修改
|
||||
dz=2.0, #需修改
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
size_x=7.0,
|
||||
size_y=7.0,
|
||||
size_z=0,
|
||||
make_tip=lambda: _make_tip_helper(volume=10, length=52.0, depth=45.1)
|
||||
)
|
||||
)
|
||||
def PRCXI_300ul_Tips(name: str) -> PRCXI9300TipRack:
|
||||
"""
|
||||
对应 JSON Code: ZX-001-300
|
||||
吸头盒通常比较特殊,需要定义 Tip 对象
|
||||
"""
|
||||
return PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=122.11,
|
||||
size_y=85.48, #修改
|
||||
size_z=58.23,
|
||||
model="PRCXI_300ul_Tips",
|
||||
material_info={
|
||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
||||
"Code": "ZX-001-300",
|
||||
"Name": "300μL Tip头",
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=7.97, #需要修改
|
||||
dy=5.0, #需修改
|
||||
dz=2.0, #需修改
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
size_x=7.0,
|
||||
size_y=7.0,
|
||||
size_z=0,
|
||||
make_tip=lambda: _make_tip_helper(volume=300, length=60.0, depth=51.0)
|
||||
)
|
||||
)
|
||||
def PRCXI_PCR_Plate_200uL_nonskirted(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
|
||||
"""
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=119.5,
|
||||
size_y=80.0,
|
||||
size_z=26.0,
|
||||
model="PRCXI_PCR_Plate_200uL_nonskirted",
|
||||
plate_type="non-skirted",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
||||
"Code": "ZX-023-0.2",
|
||||
"Name": "0.2ml PCR 板",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=7,
|
||||
dy=5,
|
||||
dz=0.0,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=6,
|
||||
size_y=6,
|
||||
size_z=15.17,
|
||||
bottom_type=WellBottomType.V,
|
||||
cross_section_type=CrossSectionType.CIRCLE,
|
||||
),
|
||||
)
|
||||
def PRCXI_PCR_Plate_200uL_semiskirted(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
|
||||
"""
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=126,
|
||||
size_y=86,
|
||||
size_z=21.2,
|
||||
model="PRCXI_PCR_Plate_200uL_semiskirted",
|
||||
plate_type="semi-skirted",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
||||
"Code": "ZX-023-0.2",
|
||||
"Name": "0.2ml PCR 板",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=11,
|
||||
dy=8,
|
||||
dz=0.0,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=6,
|
||||
size_y=6,
|
||||
size_z=15.17,
|
||||
bottom_type=WellBottomType.V,
|
||||
cross_section_type=CrossSectionType.CIRCLE,
|
||||
),
|
||||
)
|
||||
def PRCXI_PCR_Plate_200uL_skirted(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
|
||||
"""
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.76,
|
||||
size_y=86,
|
||||
size_z=16.1,
|
||||
model="PRCXI_PCR_Plate_200uL_skirted",
|
||||
plate_type="skirted",
|
||||
category="plate",
|
||||
material_info={
|
||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
||||
"Code": "ZX-023-0.2",
|
||||
"Name": "0.2ml PCR 板",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=11,
|
||||
dy=8.49,
|
||||
dz=0.8,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=6,
|
||||
size_y=6,
|
||||
size_z=15.1,
|
||||
bottom_type=WellBottomType.V,
|
||||
cross_section_type=CrossSectionType.CIRCLE,
|
||||
),
|
||||
)
|
||||
def PRCXI_trash(name: str = "trash") -> PRCXI9300Trash:
|
||||
"""
|
||||
对应 JSON Code: q1 (废弃槽)
|
||||
"""
|
||||
return PRCXI9300Trash(
|
||||
name="trash",
|
||||
size_x=126.59,
|
||||
size_y=84.87,
|
||||
size_z=89.5, # 修改
|
||||
category="trash",
|
||||
model="PRCXI_trash",
|
||||
material_info={
|
||||
"uuid": "730067cf07ae43849ddf4034299030e9",
|
||||
"Code": "q1",
|
||||
"Name": "废弃槽",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
}
|
||||
)
|
||||
def PRCXI_96_DeepWell(name: str) -> PRCXI9300Plate:
|
||||
"""
|
||||
对应 JSON Code: q2 (96深孔板)
|
||||
"""
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127.3,
|
||||
size_y=85.35,
|
||||
size_z=45.0, #修改
|
||||
model="PRCXI_96_DeepWell",
|
||||
material_info={
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f", # 对应 q2 uuid
|
||||
"Code": "q2",
|
||||
"Name": "96深孔板",
|
||||
"materialEnum": 0
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=10.9,
|
||||
dy=8.25,
|
||||
dz=2.0,
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
size_x=8.2,
|
||||
size_y=8.2,
|
||||
size_z=42.0,
|
||||
max_volume=2200
|
||||
)
|
||||
)
|
||||
def PRCXI_EP_Adapter(name: str) -> PRCXI9300TubeRack:
|
||||
"""
|
||||
对应 JSON Code: 1 (ep适配器)
|
||||
这是一个 4x6 的 EP 管架,适配 1.5mL/2.0mL 离心管
|
||||
"""
|
||||
ep_tube_prototype = Tube(
|
||||
name="EP_Tube_1.5mL",
|
||||
size_x=10.6,
|
||||
size_y=10.6,
|
||||
size_z=40.0, # 管子本身的高度,通常比架子孔略高或持平
|
||||
max_volume=1500,
|
||||
model="EP_Tube_1.5mL"
|
||||
)
|
||||
|
||||
# 计算 PRCXI9300TubeRack 中孔的起始位置 dx, dy
|
||||
dy_calc = 85.8 - 10.5 - (3 * 18) - 10.6
|
||||
dx_calc = 3.54
|
||||
return PRCXI9300TubeRack(
|
||||
name=name,
|
||||
size_x=128.04,
|
||||
size_y=85.8,
|
||||
size_z=42.66,
|
||||
model="PRCXI_EP_Adapter",
|
||||
category="tube_rack",
|
||||
material_info={
|
||||
"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7",
|
||||
"Code": "1",
|
||||
"Name": "ep适配器",
|
||||
"materialEnum": 0,
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Tube,
|
||||
num_items_x=6,
|
||||
num_items_y=4,
|
||||
dx=dx_calc,
|
||||
dy=dy_calc,
|
||||
dz=42.66 - 38.08, # 架高 - 孔深
|
||||
item_dx=21.0,
|
||||
item_dy=18.0,
|
||||
size_x=10.6,
|
||||
size_y=10.6,
|
||||
size_z=40.0,
|
||||
max_volume=1500
|
||||
)
|
||||
)
|
||||
# =========================================================================
|
||||
# 无实物,需要测量
|
||||
# =========================================================================
|
||||
def PRCXI_Tip1250_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-58-1250 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=128,
|
||||
size_y=85,
|
||||
size_z=20,
|
||||
material_info={
|
||||
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
|
||||
"Code": "ZX-58-1250",
|
||||
"Name": "Tip头适配器 1250uL",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_Tip300_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-58-300 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=127,
|
||||
size_y=85,
|
||||
size_z=81,
|
||||
material_info={
|
||||
"uuid": "7c822592b360451fb59690e49ac6b181",
|
||||
"Code": "ZX-58-300",
|
||||
"Name": "ZHONGXI 适配器 300uL",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_Tip10_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-58-10 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=128,
|
||||
size_y=85,
|
||||
size_z=72.3,
|
||||
material_info={
|
||||
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
|
||||
"Code": "ZX-58-10",
|
||||
"Name": "吸头10ul 适配器",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_1250uL_Tips(name: str) -> PRCXI9300TipRack:
|
||||
""" Code: ZX-001-1250 """
|
||||
return PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=118.09,
|
||||
size_y=80.7,
|
||||
size_z=107.67,
|
||||
model="PRCXI_1250uL_Tips",
|
||||
material_info={
|
||||
"uuid": "7960f49ddfe9448abadda89bd1556936",
|
||||
"Code": "ZX-001-1250",
|
||||
"Name": "1250μL Tip头",
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=9.545 - 7.95/2,
|
||||
dy=8.85 - 7.95/2,
|
||||
dz=2.0,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=7.0,
|
||||
size_y=7.0,
|
||||
size_z=0,
|
||||
make_tip=lambda: _make_tip_helper(volume=1250, length=107.67, depth=8)
|
||||
)
|
||||
)
|
||||
def PRCXI_10uL_Tips(name: str) -> PRCXI9300TipRack:
|
||||
""" Code: ZX-001-10 """
|
||||
return PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=120.98,
|
||||
size_y=82.12,
|
||||
size_z=67,
|
||||
model="PRCXI_10uL_Tips",
|
||||
material_info={
|
||||
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
|
||||
"Code": "ZX-001-10",
|
||||
"Name": "10μL Tip头",
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=10.99 - 5/2,
|
||||
dy=9.56 - 5/2,
|
||||
dz=2.0,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=7.0,
|
||||
size_y=7.0,
|
||||
size_z=0,
|
||||
make_tip=lambda: _make_tip_helper(volume=1250, length=52.0, depth=5)
|
||||
)
|
||||
)
|
||||
def PRCXI_1000uL_Tips(name: str) -> PRCXI9300TipRack:
|
||||
""" Code: ZX-001-1000 """
|
||||
return PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=128.09,
|
||||
size_y=85.8,
|
||||
size_z=98,
|
||||
model="PRCXI_1000uL_Tips",
|
||||
material_info={
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=14.5 - 7.95/2,
|
||||
dy=7.425,
|
||||
dz=2.0,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=7.0,
|
||||
size_y=7.0,
|
||||
size_z=0,
|
||||
make_tip=lambda: _make_tip_helper(volume=1000, length=55.0, depth=8)
|
||||
)
|
||||
)
|
||||
def PRCXI_200uL_Tips(name: str) -> PRCXI9300TipRack:
|
||||
""" Code: ZX-001-200 """
|
||||
return PRCXI9300TipRack(
|
||||
name=name,
|
||||
size_x=120.98,
|
||||
size_y=82.12,
|
||||
size_z=66.9,
|
||||
model="PRCXI_200uL_Tips",
|
||||
material_info={
|
||||
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
|
||||
"Code": "ZX-001-200",
|
||||
"Name": "200μL Tip头",
|
||||
"SupplyType": 1},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=10.99 - 5.5/2,
|
||||
dy=9.56 - 5.5/2,
|
||||
dz=2.0,
|
||||
item_dx=9,
|
||||
item_dy=9,
|
||||
size_x=7.0,
|
||||
size_z=0,
|
||||
size_y=7.0,
|
||||
make_tip=lambda: _make_tip_helper(volume=200, length=52.0, depth=5)
|
||||
)
|
||||
)
|
||||
def PRCXI_PCR_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
"""
|
||||
对应 JSON Code: ZX-58-0001 (全裙边 PCR适配器)
|
||||
"""
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=127.76,
|
||||
size_y=85.48,
|
||||
size_z=21.69,
|
||||
model="PRCXI_PCR_Adapter",
|
||||
material_info={
|
||||
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
|
||||
"Code": "ZX-58-0001",
|
||||
"Name": "全裙边 PCR适配器",
|
||||
"materialEnum": 3,
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_Reservoir_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-ADP-001 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=133,
|
||||
size_y=91.8,
|
||||
size_z=70,
|
||||
material_info={
|
||||
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
|
||||
"Code": "ZX-ADP-001",
|
||||
"Name": "储液槽 适配器",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_Deep300_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-002-300 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=136.4,
|
||||
size_y=93.8,
|
||||
size_z=96,
|
||||
material_info={
|
||||
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
|
||||
"Code": "ZX-002-300",
|
||||
"Name": "300ul深孔板适配器",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_Deep10_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-002-10 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=136.5,
|
||||
size_y=93.8,
|
||||
size_z=121.5,
|
||||
material_info={
|
||||
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
|
||||
"Code": "ZX-002-10",
|
||||
"Name": "10ul专用深孔板适配器",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: Fhh478 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=120,
|
||||
size_y=90,
|
||||
size_z=86,
|
||||
material_info={
|
||||
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
|
||||
"Code": "Fhh478",
|
||||
"Name": "适配器",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
def PRCXI_48_DeepWell(name: str) -> PRCXI9300Plate:
|
||||
""" Code: 22 (48孔深孔板) """
|
||||
print("Warning: Code '22' (48孔深孔板) dimensions are null in JSON.")
|
||||
return PRCXI9300Plate(
|
||||
name=name,
|
||||
size_x=127,
|
||||
size_y=85,
|
||||
size_z=44,
|
||||
model="PRCXI_48_DeepWell",
|
||||
material_info={
|
||||
"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b",
|
||||
"Code": "22",
|
||||
"Name": "48孔深孔板",
|
||||
"SupplyType": 1
|
||||
},
|
||||
ordered_items=create_ordered_items_2d(
|
||||
Well,
|
||||
num_items_x=6,
|
||||
num_items_y=8,
|
||||
dx=10,
|
||||
dy=10,
|
||||
dz=1,
|
||||
item_dx=18.5,
|
||||
item_dy=9,
|
||||
size_x=8,
|
||||
size_y=8,
|
||||
size_z=40
|
||||
)
|
||||
)
|
||||
def PRCXI_30mm_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||||
""" Code: ZX-58-30 """
|
||||
return PRCXI9300PlateAdapter(
|
||||
name=name,
|
||||
size_x=132,
|
||||
size_y=93.5,
|
||||
size_z=30,
|
||||
material_info={
|
||||
"uuid": "a0757a90d8e44e81a68f306a608694f2",
|
||||
"Code": "ZX-58-30",
|
||||
"Name": "30mm适配器",
|
||||
"SupplyType": 2
|
||||
}
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"Tip头适配器 1250uL": {"uuid": "3b6f33ffbf734014bcc20e3c63e124d4", "materialEnum": "0"},
|
||||
"ZHONGXI 适配器 300uL": {"uuid": "7c822592b360451fb59690e49ac6b181", "materialEnum": "0"},
|
||||
"吸头10ul 适配器": {"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c", "materialEnum": "0"},
|
||||
"1250μL Tip头": {"uuid": "7960f49ddfe9448abadda89bd1556936", "materialEnum": "0"},
|
||||
"10μL Tip头": {"uuid": "45f2ed3ad925484d96463d675a0ebf66", "materialEnum": "0"},
|
||||
"10μL加长 Tip头": {"uuid": "068b3815e36b4a72a59bae017011b29f", "materialEnum": "1"},
|
||||
"1000μL Tip头": {"uuid": "80652665f6a54402b2408d50b40398df", "materialEnum": "1"},
|
||||
"300μL Tip头": {"uuid": "076250742950465b9d6ea29a225dfb00", "materialEnum": "1"},
|
||||
"200μL Tip头": {"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7", "materialEnum": "0"},
|
||||
"0.2ml PCR板": {"uuid": "73bb9b10bc394978b70e027bf45ce2d3", "materialEnum": "0"},
|
||||
"2.2ml 深孔板": {"uuid": "ca877b8b114a4310b429d1de4aae96ee", "materialEnum": "0"},
|
||||
"储液槽": {"uuid": "04211a2dc93547fe9bf6121eac533650", "materialEnum": "0"},
|
||||
"全裙边 PCR适配器": {"uuid": "4a043a07c65a4f9bb97745e1f129b165", "materialEnum": "3"},
|
||||
"储液槽 适配器": {"uuid": "6bdfdd7069df453896b0806df50f2f4d", "materialEnum": "0"},
|
||||
"300ul深孔板适配器": {"uuid": "9a439bed8f3344549643d6b3bc5a5eb4", "materialEnum": "0"},
|
||||
"10ul专用深孔板适配器": {"uuid": "4dc8d6ecfd0449549683b8ef815a861b", "materialEnum": "0"},
|
||||
"爱津": {"uuid": "b01627718d3341aba649baa81c2c083c", "materialEnum": "0"},
|
||||
"适配器": {"uuid": "adfabfffa8f24af5abfbba67b8d0f973", "materialEnum": "0"},
|
||||
"废弃槽": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": "0"},
|
||||
"96深孔板": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": "0"},
|
||||
"384板": {"uuid": "853dcfb6226f476e8b23c250217dc7da", "materialEnum": "0"},
|
||||
"4道储液槽": {"uuid": "01953864f6f140ccaa8ddffd4f3e46f5", "materialEnum": "0"},
|
||||
"48孔深孔板": {"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b", "materialEnum": "2"},
|
||||
"12道储液槽": {"uuid": "0f1639987b154e1fac78f4fb29a1f7c1", "materialEnum": "0"},
|
||||
"HPLC料盘": {"uuid": "548bbc3df0d4447586f2c19d2c0c0c55", "materialEnum": "0"},
|
||||
"ep适配器": {"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7", "materialEnum": "0"},
|
||||
"30mm适配器": {"uuid": "a0757a90d8e44e81a68f306a608694f2", "materialEnum": "0"},
|
||||
"细菌培养皿": {"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f", "materialEnum": "4"},
|
||||
"96 细胞培养皿":{ "uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": "0"}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import collections
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
|
||||
|
||||
|
||||
prcxi_materials_path = str(Path(__file__).parent / "prcxi_material.json")
|
||||
with open(prcxi_materials_path, mode="r", encoding="utf-8") as f:
|
||||
prcxi_materials = json.loads(f.read())
|
||||
|
||||
|
||||
def tip_adaptor_1250ul(name="Tip头适配器 1250uL") -> PRCXI9300Container: # 必须传入一个name参数,是plr的规范要求
|
||||
# tip_rack = PRCXI9300Container(name, prcxi_materials["name"]["Height"])
|
||||
tip_rack = PRCXI9300Container(name, 1000,400,800, "tip_rack", collections.OrderedDict())
|
||||
tip_rack.load_state({
|
||||
"Materials": {"uuid": "7960f49ddfe9448abadda89bd1556936", "materialEnum": "0"}
|
||||
})
|
||||
return tip_rack
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import collections
|
||||
|
||||
from pylabrobot.resources import opentrons_96_tiprack_10ul
|
||||
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container, PRCXI9300Trash
|
||||
|
||||
|
||||
def get_well_container(name: str) -> PRCXI9300Container:
|
||||
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||
plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||
ordering=collections.OrderedDict())
|
||||
plate_serialized = plate.serialize()
|
||||
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||
return new_plate
|
||||
|
||||
def get_tip_rack(name: str) -> PRCXI9300Container:
|
||||
tip_racks = opentrons_96_tiprack_10ul("name").serialize()
|
||||
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
|
||||
ordering=collections.OrderedDict())
|
||||
tip_rack_serialized = tip_rack.serialize()
|
||||
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||
return new_tip_rack
|
||||
|
||||
def prcxi_96_wellplate_360ul_flat(name: str):
|
||||
return get_well_container(name)
|
||||
|
||||
def prcxi_opentrons_96_tiprack_10ul(name: str):
|
||||
return get_tip_rack(name)
|
||||
|
||||
def prcxi_trash(name: str = None):
|
||||
return PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
test_plate = prcxi_96_wellplate_360ul_flat("test_plate")
|
||||
test_rack = prcxi_opentrons_96_tiprack_10ul("test_rack")
|
||||
tash = prcxi_trash("trash")
|
||||
print(test_plate)
|
||||
print(test_rack)
|
||||
print(tash)
|
||||
# Output will be a dictionary representation of the PRCXI9300Container with well details
|
||||
@@ -0,0 +1,560 @@
|
||||
# 新威电池测试系统 - OSS 上传功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本次更新为新威电池测试系统添加了**阿里云 OSS 文件上传功能**,采用统一的 API 方式,允许将测试数据备份文件上传到云端存储。
|
||||
|
||||
## 版本更新说明
|
||||
|
||||
### ⚠️ 重大变更(2025-12-17)
|
||||
|
||||
本次更新将 OSS 上传方式从 **`oss2` 库** 改为 **统一 API 方式**,实现与团队其他系统的统一。
|
||||
|
||||
**主要变化**:
|
||||
- ✅ 用 `requests` 库
|
||||
- ✅ 通过统一 API 获取预签名 URL 进行上传
|
||||
- ✅ 简化环境变量配置(仅需要 JWT Token)
|
||||
- ✅ 返回文件访问 URL
|
||||
|
||||
## 主要改动
|
||||
|
||||
### 1. OSS 上传工具函数重构(第30-200行)
|
||||
|
||||
#### 新增函数
|
||||
|
||||
- **`get_upload_token(base_url, auth_token, scene, filename)`**
|
||||
从统一 API 获取文件上传的预签名 URL
|
||||
|
||||
- **`upload_file_with_presigned_url(upload_info, file_path)`**
|
||||
使用预签名 URL 上传文件到 OSS
|
||||
|
||||
#### 更新的函数
|
||||
|
||||
- **`upload_file_to_oss(local_file_path, oss_object_name)`**
|
||||
上传单个文件到阿里云 OSS(使用统一 API 方式)
|
||||
- 返回值变更:成功时返回文件访问 URL,失败时返回 `False`
|
||||
|
||||
- **`upload_files_to_oss(file_paths, oss_prefix)`**
|
||||
批量上传文件列表
|
||||
- `oss_prefix` 参数保留但暂不使用(接口兼容性)
|
||||
|
||||
- **`upload_directory_to_oss(local_dir, oss_prefix)`**
|
||||
上传整个目录
|
||||
- 简化实现,直接使用文件名上传
|
||||
|
||||
### 2. 环境变量配置简化
|
||||
|
||||
#### 新方式(推荐)
|
||||
```bash
|
||||
# ✅ 必需
|
||||
UNI_LAB_AUTH_TOKEN # API Key 格式: "Api xxxxxx"
|
||||
|
||||
# ✅ 可选(有默认值)
|
||||
UNI_LAB_BASE_URL (默认: https://uni-lab.test.bohrium.com)
|
||||
UNI_LAB_UPLOAD_SCENE (默认: job,其他值会被改成 default)
|
||||
```
|
||||
|
||||
### 3. 初始化方法(保持不变)
|
||||
|
||||
`__init__` 方法中的 OSS 相关配置参数:
|
||||
|
||||
```python
|
||||
# OSS 上传配置
|
||||
self.oss_upload_enabled = False # 默认不启用 OSS 上传
|
||||
self.oss_prefix = "neware_backup" # OSS 对象路径前缀
|
||||
self._last_backup_dir = None # 记录最近一次的 backup_dir
|
||||
```
|
||||
|
||||
**默认行为**:OSS 上传功能默认关闭(`oss_upload_enabled=False`),不影响现有系统。
|
||||
|
||||
### 4. upload_backup_to_oss 方法(保持不变)
|
||||
|
||||
```python
|
||||
def upload_backup_to_oss(
|
||||
self,
|
||||
backup_dir: str = None,
|
||||
file_pattern: str = "*",
|
||||
oss_prefix: str = None
|
||||
) -> dict
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 前置条件
|
||||
|
||||
#### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
# requests 库(通常已安装)
|
||||
pip install requests
|
||||
```
|
||||
|
||||
#### 2. 配置环境变量
|
||||
|
||||
根据您使用的终端类型配置环境变量:
|
||||
|
||||
##### PowerShell(推荐)
|
||||
|
||||
```powershell
|
||||
# 必需:设置认证 Token(API Key 格式)
|
||||
$env:UNI_LAB_AUTH_TOKEN = "Api xxxx"
|
||||
|
||||
# 可选:自定义服务器地址(默认为 test 环境)
|
||||
$env:UNI_LAB_BASE_URL = "https://uni-lab.test.bohrium.com"
|
||||
|
||||
# 可选:自定义上传场景(默认为 job)
|
||||
$env:UNI_LAB_UPLOAD_SCENE = "job"
|
||||
|
||||
# 验证是否设置成功
|
||||
echo $env:UNI_LAB_AUTH_TOKEN
|
||||
```
|
||||
|
||||
##### CMD / 命令提示符
|
||||
|
||||
```cmd
|
||||
REM 必需:设置认证 Token(API Key 格式)
|
||||
set UNI_LAB_AUTH_TOKEN=Api xxxx
|
||||
|
||||
REM 可选:自定义配置
|
||||
set UNI_LAB_BASE_URL=https://uni-lab.test.bohrium.com
|
||||
set UNI_LAB_UPLOAD_SCENE=job
|
||||
|
||||
REM 验证是否设置成功
|
||||
echo %UNI_LAB_AUTH_TOKEN%
|
||||
```
|
||||
|
||||
##### Linux/Mac
|
||||
|
||||
```bash
|
||||
# 必需:设置认证 Token(API Key 格式)
|
||||
export UNI_LAB_AUTH_TOKEN="Api xxxx"
|
||||
|
||||
# 可选:自定义配置
|
||||
export UNI_LAB_BASE_URL="https://uni-lab.test.bohrium.com"
|
||||
export UNI_LAB_UPLOAD_SCENE="job"
|
||||
|
||||
# 验证是否设置成功
|
||||
echo $UNI_LAB_AUTH_TOKEN
|
||||
```
|
||||
|
||||
#### 3. 获取认证 Token
|
||||
|
||||
> **重要**:从 Uni-Lab 主页 → 账号安全 中获取 API Key。
|
||||
|
||||
**获取步骤**:
|
||||
1. 登录 Uni-Lab 系统
|
||||
2. 进入主页 → 账号安全
|
||||
3. 复制 API Key
|
||||
|
||||
Token 格式示例:
|
||||
```
|
||||
Api 48ccxx336fba44f39e1e37db93xxxxx
|
||||
```
|
||||
|
||||
> **提示**:
|
||||
> - 如果 Token 已经包含 `Api ` 前缀,直接使用
|
||||
> - 如果没有前缀,代码会自动添加 `Api ` 前缀
|
||||
> - 旧版 `Bearer` JWT Token 格式仍然兼容
|
||||
|
||||
#### 4. 持久化配置(可选)
|
||||
|
||||
**临时配置**:上述命令设置的环境变量只在当前终端会话中有效。
|
||||
|
||||
**持久化方式 1:PowerShell 配置文件**
|
||||
```powershell
|
||||
# 编辑 PowerShell 配置文件
|
||||
notepad $PROFILE
|
||||
|
||||
# 在打开的文件中添加:
|
||||
$env:UNI_LAB_AUTH_TOKEN = "Api 你的API_Key"
|
||||
```
|
||||
|
||||
**持久化方式 2:Windows 系统环境变量**
|
||||
- 右键"此电脑" → "属性" → "高级系统设置" → "环境变量"
|
||||
- 添加用户变量或系统变量:
|
||||
- 变量名:`UNI_LAB_AUTH_TOKEN`
|
||||
- 变量值:`Api 你的API_Key`
|
||||
|
||||
### 使用流程
|
||||
|
||||
#### 步骤 1:启用 OSS 上传功能
|
||||
|
||||
**推荐方式:在 `device.json` 中配置**
|
||||
|
||||
编辑设备配置文件 `unilabos/devices/neware_battery_test_system/device.json`,在 `config` 中添加:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
||||
"config": {
|
||||
"ip": "127.0.0.1",
|
||||
"port": 502,
|
||||
"machine_id": 1,
|
||||
"oss_upload_enabled": true,
|
||||
"oss_prefix": "neware_backup/2025-12"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
- `oss_upload_enabled`: 设置为 `true` 启用 OSS 上传
|
||||
- `oss_prefix`: OSS 文件路径前缀,建议按日期或项目组织(暂时未使用,保留接口兼容性)
|
||||
|
||||
**其他方式:通过初始化参数**
|
||||
|
||||
```python
|
||||
device = NewareBatteryTestSystem(
|
||||
ip="127.0.0.1",
|
||||
port=502,
|
||||
oss_upload_enabled=True, # 启用 OSS 上传
|
||||
oss_prefix="neware_backup/2025-12" # 可选:自定义路径前缀
|
||||
)
|
||||
```
|
||||
|
||||
**配置完成后,重启 ROS 节点使配置生效。**
|
||||
|
||||
#### 步骤 2:提交测试任务
|
||||
|
||||
使用 `submit_from_csv` 提交测试任务:
|
||||
|
||||
```python
|
||||
result = device.submit_from_csv(
|
||||
csv_path="test_data.csv",
|
||||
output_dir="D:/neware_output"
|
||||
)
|
||||
```
|
||||
|
||||
此时会创建以下目录结构:
|
||||
```
|
||||
D:/neware_output/
|
||||
├── xml_dir/ # XML 配置文件
|
||||
└── backup_dir/ # 测试数据备份(由新威设备生成)
|
||||
```
|
||||
|
||||
#### 步骤 3:等待测试完成
|
||||
|
||||
等待新威设备完成测试,备份文件会生成到 `backup_dir` 中。
|
||||
|
||||
#### 步骤 4:上传备份文件到 OSS
|
||||
|
||||
**方法 A:使用默认设置(推荐)**
|
||||
```python
|
||||
# 自动使用最近一次的 backup_dir,上传所有文件
|
||||
result = device.upload_backup_to_oss()
|
||||
```
|
||||
|
||||
**方法 B:指定备份目录**
|
||||
```python
|
||||
# 手动指定备份目录
|
||||
result = device.upload_backup_to_oss(
|
||||
backup_dir="D:/neware_output/backup_dir"
|
||||
)
|
||||
```
|
||||
|
||||
**方法 C:筛选特定文件**
|
||||
```python
|
||||
# 仅上传 CSV 文件
|
||||
result = device.upload_backup_to_oss(
|
||||
backup_dir="D:/neware_output/backup_dir",
|
||||
file_pattern="*.csv"
|
||||
)
|
||||
|
||||
# 仅上传特定电池编号的文件
|
||||
result = device.upload_backup_to_oss(
|
||||
file_pattern="Battery_A001_*.nda"
|
||||
)
|
||||
```
|
||||
|
||||
### 返回结果示例
|
||||
|
||||
**成功上传所有文件**:
|
||||
```python
|
||||
{
|
||||
"return_info": "全部上传成功: 15/15 个文件",
|
||||
"success": True,
|
||||
"uploaded_count": 15,
|
||||
"total_count": 15,
|
||||
"failed_files": [],
|
||||
"uploaded_files": [
|
||||
{
|
||||
"filename": "Battery_A001.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
||||
},
|
||||
{
|
||||
"filename": "Battery_A002.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
||||
}
|
||||
# ... 其他 13 个文件
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**部分上传成功**:
|
||||
```python
|
||||
{
|
||||
"return_info": "部分上传成功: 12/15 个文件,失败 3 个",
|
||||
"success": True,
|
||||
"uploaded_count": 12,
|
||||
"total_count": 15,
|
||||
"failed_files": ["Battery_A003.csv", "Battery_A007.csv", "test.log"],
|
||||
"uploaded_files": [
|
||||
{
|
||||
"filename": "Battery_A001.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
||||
},
|
||||
{
|
||||
"filename": "Battery_A002.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
||||
}
|
||||
# ... 其他 10 个成功上传的文件
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **说明**:`uploaded_files` 字段包含所有成功上传文件的详细信息:
|
||||
> - `filename`: 文件名(不含路径)
|
||||
> - `url`: 文件在 OSS 上的完整访问 URL
|
||||
|
||||
## 错误处理
|
||||
|
||||
### OSS 上传未启用
|
||||
|
||||
如果 `oss_upload_enabled=False`,调用 `upload_backup_to_oss` 会返回:
|
||||
```python
|
||||
{
|
||||
"return_info": "OSS 上传未启用 (oss_upload_enabled=False),跳过上传。备份目录: ...",
|
||||
"success": False,
|
||||
"uploaded_count": 0,
|
||||
"total_count": 0,
|
||||
"failed_files": []
|
||||
}
|
||||
```
|
||||
|
||||
**解决方法**:设置 `device.oss_upload_enabled = True`
|
||||
|
||||
### 环境变量未配置
|
||||
|
||||
如果缺少 `UNI_LAB_AUTH_TOKEN`,会返回:
|
||||
```python
|
||||
{
|
||||
"return_info": "OSS 环境变量配置错误: 请设置环境变量: UNI_LAB_AUTH_TOKEN",
|
||||
"success": False,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**解决方法**:按照前置条件配置环境变量
|
||||
|
||||
### 备份目录不存在
|
||||
|
||||
如果指定的备份目录不存在,会返回:
|
||||
```python
|
||||
{
|
||||
"return_info": "备份目录不存在: D:/neware_output/backup_dir",
|
||||
"success": False,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**解决方法**:检查目录路径是否正确,或等待测试生成备份文件
|
||||
|
||||
### API 认证失败
|
||||
|
||||
如果 Token 无效或过期,会返回:
|
||||
```python
|
||||
{
|
||||
"return_info": "获取凭证失败: 认证失败",
|
||||
"success": False,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**解决方法**:检查 Token 是否正确,或联系开发团队获取新 Token
|
||||
|
||||
## 技术细节
|
||||
|
||||
### OSS 上传流程(新方式)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[开始上传] --> B[验证配置和环境变量]
|
||||
B --> C[扫描备份目录]
|
||||
C --> D[筛选符合 pattern 的文件]
|
||||
D --> E[遍历每个文件]
|
||||
E --> F[调用 API 获取预签名 URL]
|
||||
F --> G{获取成功?}
|
||||
G -->|是| H[使用预签名 URL 上传文件]
|
||||
G -->|否| I[记录失败]
|
||||
H --> J{上传成功?}
|
||||
J -->|是| K[记录成功 + 文件 URL]
|
||||
J -->|否| I
|
||||
I --> L{还有文件?}
|
||||
K --> L
|
||||
L -->|是| E
|
||||
L -->|否| M[返回统计结果]
|
||||
```
|
||||
|
||||
### 上传 API 流程
|
||||
|
||||
1. **获取预签名 URL**
|
||||
- 请求:`GET /api/v1/applications/token?scene={scene}&filename={filename}`
|
||||
- 认证:`Authorization: Bearer {token}`
|
||||
- 响应:`{code: 0, data: {url: "预签名URL", path: "文件路径"}}`
|
||||
|
||||
2. **上传文件**
|
||||
- 请求:`PUT {预签名URL}`
|
||||
- 内容:文件二进制数据
|
||||
- 响应:HTTP 200 表示成功
|
||||
|
||||
3. **生成访问 URL**
|
||||
- 格式:`https://{OSS_PUBLIC_HOST}/{path}`
|
||||
- 示例:`https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/battery_data.csv`
|
||||
|
||||
### 日志记录
|
||||
|
||||
所有上传操作都会通过 ROS 日志系统记录:
|
||||
- `INFO` 级别:上传进度和成功信息
|
||||
- `WARNING` 级别:空目录、未启用等警告
|
||||
- `ERROR` 级别:上传失败、配置错误
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **上传时机**:`backup_dir` 中的文件是在新威设备执行测试过程中实时生成的,请确保测试已完成再上传。
|
||||
|
||||
2. **文件命名**:上传到 OSS 的文件会保留原始文件名,路径由统一 API 分配。
|
||||
|
||||
3. **网络要求**:上传需要稳定的网络连接到阿里云 OSS 服务。
|
||||
|
||||
4. **Token 有效期**:JWT Token 有过期时间,过期后需要重新获取。
|
||||
|
||||
5. **成本考虑**:OSS 存储和流量会产生费用,请根据需要合理设置文件筛选规则。
|
||||
|
||||
6. **并发上传**:当前实现为串行上传,大量文件上传可能需要较长时间。
|
||||
|
||||
7. **文件大小限制**:请注意单个文件大小是否有上传限制(由统一 API 控制)。
|
||||
|
||||
## 兼容性
|
||||
|
||||
- ✅ **向后兼容**:默认 `oss_upload_enabled=False`,不影响现有系统
|
||||
- ✅ **可选功能**:仅在需要时启用
|
||||
- ✅ **独立操作**:上传失败不会影响测试任务的提交和执行
|
||||
- ⚠️ **环境变量变更**:需要更新环境变量配置(从 OSS AK/SK 改为 JWT Token)
|
||||
|
||||
## 迁移指南
|
||||
|
||||
如果您之前使用 `oss2` 库方式,请按以下步骤迁移:
|
||||
|
||||
### 1. 卸载旧依赖(可选)
|
||||
```bash
|
||||
pip uninstall oss2
|
||||
```
|
||||
|
||||
### 2. 删除旧环境变量
|
||||
```powershell
|
||||
# PowerShell
|
||||
Remove-Item Env:\OSS_ACCESS_KEY_ID
|
||||
Remove-Item Env:\OSS_ACCESS_KEY_SECRET
|
||||
Remove-Item Env:\OSS_BUCKET_NAME
|
||||
Remove-Item Env:\OSS_ENDPOINT
|
||||
```
|
||||
|
||||
### 3. 设置新环境变量
|
||||
```powershell
|
||||
# PowerShell
|
||||
$env:UNI_LAB_AUTH_TOKEN = "Bearer 你的token..."
|
||||
```
|
||||
|
||||
### 4. 测试上传功能
|
||||
```python
|
||||
# 验证上传是否正常工作
|
||||
result = device.upload_backup_to_oss(backup_dir="测试目录")
|
||||
print(result)
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 为什么要从 `oss2` 改为统一 API?**
|
||||
A: 为了与团队其他系统保持一致,简化配置,并统一认证方式。
|
||||
|
||||
**Q: Token 在哪里获取?**
|
||||
A: 请联系开发团队获取有效的 JWT Token。
|
||||
|
||||
**Q: Token 过期了怎么办?**
|
||||
A: 重新获取新的 Token 并更新环境变量 `UNI_LAB_AUTH_TOKEN`。
|
||||
|
||||
**Q: 可以自定义上传路径吗?**
|
||||
A: 当前版本路径由统一 API 自动分配,`oss_prefix` 参数暂不使用(保留接口兼容性)。
|
||||
|
||||
**Q: 为什么不在 `submit_from_csv` 中自动上传?**
|
||||
A: 因为备份文件在测试进行中逐步生成,方法返回时可能文件尚未完全生成,因此提供独立的上传方法更灵活。
|
||||
|
||||
**Q: 上传后如何访问文件?**
|
||||
A: 上传成功后会返回文件访问 URL,格式为 `https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/{path}`
|
||||
|
||||
**Q: 如何删除已上传的文件?**
|
||||
A: 需要通过 OSS 控制台或 API 操作,本功能仅负责上传。
|
||||
|
||||
## 验证上传结果
|
||||
|
||||
### 方法1:通过阿里云控制台查看
|
||||
|
||||
1. 登录 [阿里云 OSS 控制台](https://oss.console.aliyun.com/)
|
||||
2. 点击左侧 **Bucket列表**
|
||||
3. 选择 `uni-lab-test` Bucket
|
||||
4. 点击 **文件管理**
|
||||
5. 查看上传的文件列表
|
||||
|
||||
### 方法2:使用返回的文件 URL
|
||||
|
||||
上传成功后,`upload_file_to_oss()` 会返回文件访问 URL:
|
||||
```python
|
||||
url = upload_file_to_oss("local_file.csv")
|
||||
print(f"文件访问 URL: {url}")
|
||||
# 输出示例:https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/local_file.csv
|
||||
```
|
||||
|
||||
> **注意**:OSS 文件默认为私有访问,直接访问 URL 可能需要签名认证。
|
||||
|
||||
### 方法3:使用 ossutil 命令行工具
|
||||
|
||||
安装 [ossutil](https://help.aliyun.com/document_detail/120075.html) 后:
|
||||
|
||||
```bash
|
||||
# 列出文件
|
||||
ossutil ls oss://uni-lab-test/job/
|
||||
|
||||
# 下载文件到本地
|
||||
ossutil cp oss://uni-lab-test/job/20251217/文件名 ./本地路径
|
||||
|
||||
# 生成签名URL(有效期1小时)
|
||||
ossutil sign oss://uni-lab-test/job/20251217/文件名 --timeout 3600
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
- **2025-12-17**: v2.0(重大更新)
|
||||
- ⚠️ 从 `oss2` 库改为统一 API 方式
|
||||
- 简化环境变量配置(仅需 JWT Token)
|
||||
- 新增 `get_upload_token()` 和 `upload_file_with_presigned_url()` 函数
|
||||
- `upload_file_to_oss()` 返回值改为文件访问 URL
|
||||
- 更新文档和迁移指南
|
||||
|
||||
- **2025-12-15**: v1.1
|
||||
- 添加初始化参数 `oss_upload_enabled` 和 `oss_prefix`
|
||||
- 支持在 `device.json` 中配置 OSS 上传
|
||||
- 更新使用说明,添加验证方法
|
||||
|
||||
- **2025-12-13**: v1.0 初始版本
|
||||
- 添加 OSS 上传工具函数(基于 `oss2` 库)
|
||||
- 创建 `upload_backup_to_oss` 动作方法
|
||||
- 支持文件筛选和自定义 OSS 路径
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Uni-Lab 统一文件上传 API 文档](https://uni-lab.test.bohrium.com/api/docs)(如有)
|
||||
- [阿里云 OSS 控制台](https://oss.console.aliyun.com/)
|
||||
- [ossutil 工具文档](https://help.aliyun.com/document_detail/120075.html)
|
||||
@@ -0,0 +1,574 @@
|
||||
# Neware Battery Test System - OSS Upload Feature
|
||||
|
||||
## Overview
|
||||
|
||||
This update adds **Aliyun OSS file upload functionality** to the Neware Battery Test System using a unified API approach, allowing test data backup files to be uploaded to cloud storage.
|
||||
|
||||
## Version Updates
|
||||
|
||||
### ⚠️ Breaking Changes (2025-12-17)
|
||||
|
||||
This update changes the OSS upload method from **`oss2` library** to **unified API approach** to align with other team systems.
|
||||
|
||||
**Main Changes**:
|
||||
- ✅ Use `requests` library
|
||||
- ✅ Upload via presigned URLs obtained through unified API
|
||||
- ✅ Simplified environment variable configuration (only API Key required)
|
||||
- ✅ Returns file access URLs
|
||||
|
||||
## Main Changes
|
||||
|
||||
### 1. OSS Upload Functions Refactored (Lines 30-200)
|
||||
|
||||
#### New Functions
|
||||
|
||||
- **`get_upload_token(base_url, auth_token, scene, filename)`**
|
||||
Obtain presigned URL for file upload from unified API
|
||||
|
||||
- **`upload_file_with_presigned_url(upload_info, file_path)`**
|
||||
Upload file to OSS using presigned URL
|
||||
|
||||
#### Updated Functions
|
||||
|
||||
- **`upload_file_to_oss(local_file_path, oss_object_name)`**
|
||||
Upload single file to Aliyun OSS (using unified API approach)
|
||||
- Return value changed: returns file access URL on success, `False` on failure
|
||||
|
||||
- **`upload_files_to_oss(file_paths, oss_prefix)`**
|
||||
Batch upload file list
|
||||
- `oss_prefix` parameter retained but not used (interface compatibility)
|
||||
|
||||
- **`upload_directory_to_oss(local_dir, oss_prefix)`**
|
||||
Upload entire directory
|
||||
- Simplified implementation, uploads using filenames directly
|
||||
|
||||
### 2. Simplified Environment Variable Configuration
|
||||
|
||||
#### Old Method (Deprecated)
|
||||
```bash
|
||||
# ❌ No longer used
|
||||
OSS_ACCESS_KEY_ID
|
||||
OSS_ACCESS_KEY_SECRET
|
||||
OSS_BUCKET_NAME
|
||||
OSS_ENDPOINT
|
||||
```
|
||||
|
||||
#### New Method (Recommended)
|
||||
```bash
|
||||
# ✅ Required
|
||||
UNI_LAB_AUTH_TOKEN # API Key format: "Api xxxxxx"
|
||||
|
||||
# ✅ Optional (with defaults)
|
||||
UNI_LAB_BASE_URL (default: https://uni-lab.test.bohrium.com)
|
||||
UNI_LAB_UPLOAD_SCENE (default: job, other values will be changed to default)
|
||||
```
|
||||
|
||||
### 3. Initialization Method (Unchanged)
|
||||
|
||||
OSS-related configuration parameters in `__init__` method:
|
||||
|
||||
```python
|
||||
# OSS upload configuration
|
||||
self.oss_upload_enabled = False # OSS upload disabled by default
|
||||
self.oss_prefix = "neware_backup" # OSS object path prefix
|
||||
self._last_backup_dir = None # Record last backup_dir
|
||||
```
|
||||
|
||||
**Default Behavior**: OSS upload is disabled by default (`oss_upload_enabled=False`), does not affect existing systems.
|
||||
|
||||
### 4. upload_backup_to_oss Method (Unchanged)
|
||||
|
||||
```python
|
||||
def upload_backup_to_oss(
|
||||
self,
|
||||
backup_dir: str = None,
|
||||
file_pattern: str = "*",
|
||||
oss_prefix: str = None
|
||||
) -> dict
|
||||
```
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Prerequisites
|
||||
|
||||
#### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
# requests library (usually pre-installed)
|
||||
pip install requests
|
||||
```
|
||||
|
||||
> **Note**: No longer need to install `oss2` library
|
||||
|
||||
#### 2. Configure Environment Variables
|
||||
|
||||
Configure environment variables based on your terminal type:
|
||||
|
||||
##### PowerShell (Recommended)
|
||||
|
||||
```powershell
|
||||
# Required: Set authentication Token (API Key format)
|
||||
$env:UNI_LAB_AUTH_TOKEN = "Api xxxx"
|
||||
|
||||
# Optional: Custom server URL (defaults to test environment)
|
||||
$env:UNI_LAB_BASE_URL = "https://uni-lab.test.bohrium.com"
|
||||
|
||||
# Optional: Custom upload scene (defaults to job)
|
||||
$env:UNI_LAB_UPLOAD_SCENE = "job"
|
||||
|
||||
# Verify if set successfully
|
||||
echo $env:UNI_LAB_AUTH_TOKEN
|
||||
```
|
||||
|
||||
##### CMD / Command Prompt
|
||||
|
||||
```cmd
|
||||
REM Required: Set authentication Token (API Key format)
|
||||
set UNI_LAB_AUTH_TOKEN=Api xxxx
|
||||
|
||||
REM Optional: Custom configuration
|
||||
set UNI_LAB_BASE_URL=https://uni-lab.test.bohrium.com
|
||||
set UNI_LAB_UPLOAD_SCENE=job
|
||||
|
||||
REM Verify if set successfully
|
||||
echo %UNI_LAB_AUTH_TOKEN%
|
||||
```
|
||||
|
||||
##### Linux/Mac
|
||||
|
||||
```bash
|
||||
# Required: Set authentication Token (API Key format)
|
||||
export UNI_LAB_AUTH_TOKEN="Api xxxx"
|
||||
|
||||
# Optional: Custom configuration
|
||||
export UNI_LAB_BASE_URL="https://uni-lab.test.bohrium.com"
|
||||
export UNI_LAB_UPLOAD_SCENE="job"
|
||||
|
||||
# Verify if set successfully
|
||||
echo $UNI_LAB_AUTH_TOKEN
|
||||
```
|
||||
|
||||
#### 3. Obtain Authentication Token
|
||||
|
||||
> **Important**: Obtain API Key from Uni-Lab Homepage → Account Security.
|
||||
|
||||
**Steps to Obtain**:
|
||||
1. Login to Uni-Lab system
|
||||
2. Go to Homepage → Account Security
|
||||
3. Copy your API Key
|
||||
|
||||
Token format example:
|
||||
```
|
||||
Api 48ccxx336fba44f39e1e37db93xxxxx
|
||||
```
|
||||
|
||||
> **Tips**:
|
||||
> - If Token already includes `Api ` prefix, use directly
|
||||
> - If no prefix, code will automatically add `Api ` prefix
|
||||
> - Old `Bearer` JWT Token format is still compatible
|
||||
|
||||
#### 4. Persistent Configuration (Optional)
|
||||
|
||||
**Temporary Configuration**: Environment variables set with the above commands are only valid for the current terminal session.
|
||||
|
||||
**Persistence Method 1: PowerShell Profile**
|
||||
```powershell
|
||||
# Edit PowerShell profile
|
||||
notepad $PROFILE
|
||||
|
||||
# Add to the opened file:
|
||||
$env:UNI_LAB_AUTH_TOKEN = "Api your_API_Key"
|
||||
```
|
||||
|
||||
**Persistence Method 2: Windows System Environment Variables**
|
||||
- Right-click "This PC" → "Properties" → "Advanced system settings" → "Environment Variables"
|
||||
- Add user or system variable:
|
||||
- Variable name: `UNI_LAB_AUTH_TOKEN`
|
||||
- Variable value: `Api your_API_Key`
|
||||
|
||||
### Usage Workflow
|
||||
|
||||
#### Step 1: Enable OSS Upload Feature
|
||||
|
||||
**Recommended: Configure in `device.json`**
|
||||
|
||||
Edit device configuration file `unilabos/devices/neware_battery_test_system/device.json`, add to `config`:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
||||
"config": {
|
||||
"ip": "127.0.0.1",
|
||||
"port": 502,
|
||||
"machine_id": 1,
|
||||
"oss_upload_enabled": true,
|
||||
"oss_prefix": "neware_backup/2025-12"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter Description**:
|
||||
- `oss_upload_enabled`: Set to `true` to enable OSS upload
|
||||
- `oss_prefix`: OSS file path prefix, recommended to organize by date or project (currently unused, retained for interface compatibility)
|
||||
|
||||
**Alternative: Via Initialization Parameters**
|
||||
|
||||
```python
|
||||
device = NewareBatteryTestSystem(
|
||||
ip="127.0.0.1",
|
||||
port=502,
|
||||
oss_upload_enabled=True, # Enable OSS upload
|
||||
oss_prefix="neware_backup/2025-12" # Optional: custom path prefix
|
||||
)
|
||||
```
|
||||
|
||||
**After configuration, restart the ROS node for changes to take effect.**
|
||||
|
||||
#### Step 2: Submit Test Tasks
|
||||
|
||||
Use `submit_from_csv` to submit test tasks:
|
||||
|
||||
```python
|
||||
result = device.submit_from_csv(
|
||||
csv_path="test_data.csv",
|
||||
output_dir="D:/neware_output"
|
||||
)
|
||||
```
|
||||
|
||||
This creates the following directory structure:
|
||||
```
|
||||
D:/neware_output/
|
||||
├── xml_dir/ # XML configuration files
|
||||
└── backup_dir/ # Test data backup (generated by Neware device)
|
||||
```
|
||||
|
||||
#### Step 3: Wait for Test Completion
|
||||
|
||||
Wait for the Neware device to complete testing. Backup files will be generated in the `backup_dir`.
|
||||
|
||||
#### Step 4: Upload Backup Files to OSS
|
||||
|
||||
**Method A: Use Default Settings (Recommended)**
|
||||
```python
|
||||
# Automatically uses the last backup_dir, uploads all files
|
||||
result = device.upload_backup_to_oss()
|
||||
```
|
||||
|
||||
**Method B: Specify Backup Directory**
|
||||
```python
|
||||
# Manually specify backup directory
|
||||
result = device.upload_backup_to_oss(
|
||||
backup_dir="D:/neware_output/backup_dir"
|
||||
)
|
||||
```
|
||||
|
||||
**Method C: Filter Specific Files**
|
||||
```python
|
||||
# Upload only CSV files
|
||||
result = device.upload_backup_to_oss(
|
||||
backup_dir="D:/neware_output/backup_dir",
|
||||
file_pattern="*.csv"
|
||||
)
|
||||
|
||||
# Upload files for specific battery IDs
|
||||
result = device.upload_backup_to_oss(
|
||||
file_pattern="Battery_A001_*.nda"
|
||||
)
|
||||
```
|
||||
|
||||
### Return Result Examples
|
||||
|
||||
**All Files Uploaded Successfully**:
|
||||
```python
|
||||
{
|
||||
"return_info": "All uploads successful: 15/15 files",
|
||||
"success": True,
|
||||
"uploaded_count": 15,
|
||||
"total_count": 15,
|
||||
"failed_files": [],
|
||||
"uploaded_files": [
|
||||
{
|
||||
"filename": "Battery_A001.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
||||
},
|
||||
{
|
||||
"filename": "Battery_A002.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
||||
}
|
||||
# ... other 13 files
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Partial Upload Success**:
|
||||
```python
|
||||
{
|
||||
"return_info": "Partial upload success: 12/15 files, 3 failed",
|
||||
"success": True,
|
||||
"uploaded_count": 12,
|
||||
"total_count": 15,
|
||||
"failed_files": ["Battery_A003.csv", "Battery_A007.csv", "test.log"],
|
||||
"uploaded_files": [
|
||||
{
|
||||
"filename": "Battery_A001.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
||||
},
|
||||
{
|
||||
"filename": "Battery_A002.ndax",
|
||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
||||
}
|
||||
# ... other 10 successfully uploaded files
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: The `uploaded_files` field contains detailed information for all successfully uploaded files:
|
||||
> - `filename`: Filename (without path)
|
||||
> - `url`: Complete OSS access URL for the file
|
||||
|
||||
## Error Handling
|
||||
|
||||
### OSS Upload Not Enabled
|
||||
|
||||
If `oss_upload_enabled=False`, calling `upload_backup_to_oss` returns:
|
||||
```python
|
||||
{
|
||||
"return_info": "OSS upload not enabled (oss_upload_enabled=False), skipping upload. Backup directory: ...",
|
||||
"success": False,
|
||||
"uploaded_count": 0,
|
||||
"total_count": 0,
|
||||
"failed_files": []
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Set `device.oss_upload_enabled = True`
|
||||
|
||||
### Environment Variables Not Configured
|
||||
|
||||
If `UNI_LAB_AUTH_TOKEN` is missing, returns:
|
||||
```python
|
||||
{
|
||||
"return_info": "OSS environment variable configuration error: Please set environment variable: UNI_LAB_AUTH_TOKEN",
|
||||
"success": False,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Configure environment variables as per prerequisites
|
||||
|
||||
### Backup Directory Does Not Exist
|
||||
|
||||
If specified backup directory doesn't exist, returns:
|
||||
```python
|
||||
{
|
||||
"return_info": "Backup directory does not exist: D:/neware_output/backup_dir",
|
||||
"success": False,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Check if directory path is correct, or wait for test to generate backup files
|
||||
|
||||
### API Authentication Failed
|
||||
|
||||
If Token is invalid or expired, returns:
|
||||
```python
|
||||
{
|
||||
"return_info": "Failed to get credentials: Authentication failed",
|
||||
"success": False,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Check if Token is correct, or contact development team for new Token
|
||||
|
||||
## Technical Details
|
||||
|
||||
### OSS Upload Process (New Method)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Start Upload] --> B[Verify Configuration and Environment Variables]
|
||||
B --> C[Scan Backup Directory]
|
||||
C --> D[Filter Files Matching Pattern]
|
||||
D --> E[Iterate Each File]
|
||||
E --> F[Call API to Get Presigned URL]
|
||||
F --> G{Success?}
|
||||
G -->|Yes| H[Upload File Using Presigned URL]
|
||||
G -->|No| I[Record Failure]
|
||||
H --> J{Upload Success?}
|
||||
J -->|Yes| K[Record Success + File URL]
|
||||
J -->|No| I
|
||||
I --> L{More Files?}
|
||||
K --> L
|
||||
L -->|Yes| E
|
||||
L -->|No| M[Return Statistics]
|
||||
```
|
||||
|
||||
### Upload API Flow
|
||||
|
||||
1. **Get Presigned URL**
|
||||
- Request: `GET /api/v1/lab/storage/token?scene={scene}&filename={filename}&path={path}`
|
||||
- Authentication: `Authorization: Api {api_key}` or `Authorization: Bearer {token}`
|
||||
- Response: `{code: 0, data: {url: "presigned_url", path: "file_path"}}`
|
||||
|
||||
2. **Upload File**
|
||||
- Request: `PUT {presigned_url}`
|
||||
- Content: File binary data
|
||||
- Response: HTTP 200 indicates success
|
||||
|
||||
3. **Generate Access URL**
|
||||
- Format: `https://{OSS_PUBLIC_HOST}/{path}`
|
||||
- Example: `https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/battery_data.csv`
|
||||
|
||||
### Logging
|
||||
|
||||
All upload operations are logged through ROS logging system:
|
||||
- `INFO` level: Upload progress and success information
|
||||
- `WARNING` level: Empty directory, not enabled warnings
|
||||
- `ERROR` level: Upload failures, configuration errors
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Upload Timing**: Files in `backup_dir` are generated in real-time during test execution. Ensure testing is complete before uploading.
|
||||
|
||||
2. **File Naming**: Files uploaded to OSS retain original filenames. Paths are assigned by unified API.
|
||||
|
||||
3. **Network Requirements**: Upload requires stable network connection to Aliyun OSS service.
|
||||
|
||||
4. **Token Expiration**: JWT Tokens have expiration time. Need to obtain new token after expiration.
|
||||
|
||||
5. **Cost Considerations**: OSS storage and traffic incur costs. Set file filtering rules appropriately.
|
||||
|
||||
6. **Concurrent Upload**: Current implementation uses serial upload. Large number of files may take considerable time.
|
||||
|
||||
7. **File Size Limits**: Note single file size upload limits (controlled by unified API).
|
||||
|
||||
## Compatibility
|
||||
|
||||
- ✅ **Backward Compatible**: Default `oss_upload_enabled=False`, does not affect existing systems
|
||||
- ✅ **Optional Feature**: Enable only when needed
|
||||
- ✅ **Independent Operation**: Upload failures do not affect test task submission and execution
|
||||
- ⚠️ **Environment Variable Changes**: Need to update environment variable configuration (from OSS AK/SK to API Key)
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you previously used the `oss2` library method, follow these steps to migrate:
|
||||
|
||||
### 1. Uninstall Old Dependencies (Optional)
|
||||
```bash
|
||||
pip uninstall oss2
|
||||
```
|
||||
|
||||
### 2. Remove Old Environment Variables
|
||||
```powershell
|
||||
# PowerShell
|
||||
Remove-Item Env:\OSS_ACCESS_KEY_ID
|
||||
Remove-Item Env:\OSS_ACCESS_KEY_SECRET
|
||||
Remove-Item Env:\OSS_BUCKET_NAME
|
||||
Remove-Item Env:\OSS_ENDPOINT
|
||||
```
|
||||
|
||||
### 3. Set New Environment Variables
|
||||
```powershell
|
||||
# PowerShell
|
||||
$env:UNI_LAB_AUTH_TOKEN = "Api your_API_Key"
|
||||
```
|
||||
|
||||
### 4. Test Upload Functionality
|
||||
```python
|
||||
# Verify upload works correctly
|
||||
result = device.upload_backup_to_oss(backup_dir="test_directory")
|
||||
print(result)
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why change from `oss2` to unified API?**
|
||||
A: To maintain consistency with other team systems, simplify configuration, and unify authentication methods.
|
||||
|
||||
**Q: Where to get the Token?**
|
||||
A: Obtain API Key from Uni-Lab Homepage → Account Security.
|
||||
|
||||
**Q: What if Token expires?**
|
||||
A: Obtain a new API Key and update the `UNI_LAB_AUTH_TOKEN` environment variable.
|
||||
|
||||
**Q: Can I customize upload paths?**
|
||||
A: Current version has paths automatically assigned by unified API. `oss_prefix` parameter is currently unused (retained for interface compatibility).
|
||||
|
||||
**Q: Why not auto-upload in `submit_from_csv`?**
|
||||
A: Because backup files are generated progressively during testing, they may not be fully generated when the method returns. A separate upload method provides more flexibility.
|
||||
|
||||
**Q: How to access files after upload?**
|
||||
A: Upload success returns file access URL in format `https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/{path}`
|
||||
|
||||
**Q: How to delete uploaded files?**
|
||||
A: Need to operate through OSS console or API. This feature only handles uploads.
|
||||
|
||||
## Verifying Upload Results
|
||||
|
||||
### Method 1: Via Aliyun Console
|
||||
|
||||
1. Login to [Aliyun OSS Console](https://oss.console.aliyun.com/)
|
||||
2. Click **Bucket List** on the left
|
||||
3. Select the `uni-lab-test` Bucket
|
||||
4. Click **File Management**
|
||||
5. View uploaded file list
|
||||
|
||||
### Method 2: Using Returned File URL
|
||||
|
||||
After successful upload, `upload_file_to_oss()` returns file access URL:
|
||||
```python
|
||||
url = upload_file_to_oss("local_file.csv")
|
||||
print(f"File access URL: {url}")
|
||||
# Example output: https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/local_file.csv
|
||||
```
|
||||
|
||||
> **Note**: OSS files are private by default, direct URL access may require signature authentication.
|
||||
|
||||
### Method 3: Using ossutil CLI Tool
|
||||
|
||||
After installing [ossutil](https://help.aliyun.com/document_detail/120075.html):
|
||||
|
||||
```bash
|
||||
# List files
|
||||
ossutil ls oss://uni-lab-test/job/
|
||||
|
||||
# Download file to local
|
||||
ossutil cp oss://uni-lab-test/job/20251217/filename ./local_path
|
||||
|
||||
# Generate signed URL (valid for 1 hour)
|
||||
ossutil sign oss://uni-lab-test/job/20251217/filename --timeout 3600
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
- **2025-12-17**: v2.0 (Major Update)
|
||||
- ⚠️ Changed from `oss2` library to unified API approach
|
||||
- Simplified environment variable configuration (only API Key required)
|
||||
- Added `get_upload_token()` and `upload_file_with_presigned_url()` functions
|
||||
- `upload_file_to_oss()` return value changed to file access URL
|
||||
- Updated documentation and migration guide
|
||||
- Token format: Support both `Api Key` and `Bearer JWT`
|
||||
- API endpoint: `/api/v1/lab/storage/token`
|
||||
- Scene parameter: Fixed to `job` (other values changed to `default`)
|
||||
|
||||
- **2025-12-15**: v1.1
|
||||
- Added initialization parameters `oss_upload_enabled` and `oss_prefix`
|
||||
- Support OSS upload configuration in `device.json`
|
||||
- Updated usage guide, added verification methods
|
||||
|
||||
- **2025-12-13**: v1.0 Initial Version
|
||||
- Added OSS upload utility functions (based on `oss2` library)
|
||||
- Created `upload_backup_to_oss` action method
|
||||
- Support file filtering and custom OSS paths
|
||||
|
||||
## References
|
||||
|
||||
- [Uni-Lab Unified File Upload API Documentation](https://uni-lab.test.bohrium.com/api/docs) (if available)
|
||||
- [Aliyun OSS Console](https://oss.console.aliyun.com/)
|
||||
- [ossutil Tool Documentation](https://help.aliyun.com/document_detail/120075.html)
|
||||
35
unilabos/devices/neware_battery_test_system/device.json
Normal file
35
unilabos/devices/neware_battery_test_system/device.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
||||
"name": "Neware Battery Test System",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "neware_battery_test_system",
|
||||
"position": {
|
||||
"x": 620.0,
|
||||
"y": 200.0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"ip": "127.0.0.1",
|
||||
"port": 502,
|
||||
"machine_id": 1,
|
||||
"devtype": "27",
|
||||
"timeout": 20,
|
||||
"size_x": 500.0,
|
||||
"size_y": 500.0,
|
||||
"size_z": 2000.0,
|
||||
"oss_upload_enabled": true,
|
||||
"oss_prefix": "neware_backup/2025-12"
|
||||
},
|
||||
"data": {
|
||||
"功能说明": "新威电池测试系统,提供720通道监控和CSV批量提交功能",
|
||||
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
|
||||
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
93
unilabos/devices/workstation/post_process/bottle_carriers.py
Normal file
93
unilabos/devices/workstation/post_process/bottle_carriers.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||
|
||||
from unilabos.resources.itemized_carrier import BottleCarrier
|
||||
from unilabos.devices.workstation.post_process.bottles import POST_PROCESS_PolymerStation_Reagent_Bottle
|
||||
|
||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 聚合站(PolymerStation)载体定义(统一入口)
|
||||
# ============================================================================
|
||||
|
||||
def POST_PROCESS_Raw_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
"""聚合站-单试剂瓶载架
|
||||
|
||||
参数:
|
||||
- name: 载架名称前缀
|
||||
"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 20.0
|
||||
|
||||
# 烧杯/试剂瓶占位尺寸(使用圆形占位)
|
||||
beaker_diameter = 60.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||
center_z = 5.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=beaker_diameter,
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="POST_PROCESS_Raw_1BottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# 统一后缀采用 "flask_1" 命名(可按需调整)
|
||||
carrier[0] = POST_PROCESS_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
def POST_PROCESS_Reaction_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
"""聚合站-单试剂瓶载架
|
||||
|
||||
参数:
|
||||
- name: 载架名称前缀
|
||||
"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 20.0
|
||||
|
||||
# 烧杯/试剂瓶占位尺寸(使用圆形占位)
|
||||
beaker_diameter = 60.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||
center_z = 5.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=beaker_diameter,
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="POST_PROCESS_Reaction_1BottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# 统一后缀采用 "flask_1" 命名(可按需调整)
|
||||
carrier[0] = POST_PROCESS_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
20
unilabos/devices/workstation/post_process/bottles.py
Normal file
20
unilabos/devices/workstation/post_process/bottles.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from unilabos.resources.itemized_carrier import Bottle
|
||||
|
||||
|
||||
def POST_PROCESS_PolymerStation_Reagent_Bottle(
|
||||
name: str,
|
||||
diameter: float = 70.0,
|
||||
height: float = 120.0,
|
||||
max_volume: float = 500000.0, # 500mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建试剂瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="POST_PROCESS_PolymerStation_Reagent_Bottle",
|
||||
)
|
||||
|
||||
46
unilabos/devices/workstation/post_process/decks.py
Normal file
46
unilabos/devices/workstation/post_process/decks.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from os import name
|
||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
||||
|
||||
from unilabos.devices.workstation.post_process.warehouses import (
|
||||
post_process_warehouse_4x3x1,
|
||||
post_process_warehouse_4x3x1_2,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class post_process_deck(Deck):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "post_process_deck",
|
||||
size_x: float = 2000.0,
|
||||
size_y: float = 1000.0,
|
||||
size_z: float = 2670.0,
|
||||
category: str = "deck",
|
||||
setup: bool = True,
|
||||
) -> None:
|
||||
super().__init__(name=name, size_x=1700.0, size_y=1350.0, size_z=2670.0)
|
||||
if setup:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
self.warehouses = {
|
||||
"原料罐堆栈": post_process_warehouse_4x3x1("原料罐堆栈"),
|
||||
"反应罐堆栈": post_process_warehouse_4x3x1_2("反应罐堆栈"),
|
||||
|
||||
}
|
||||
# warehouse 的位置
|
||||
self.warehouse_locations = {
|
||||
"原料罐堆栈": Coordinate(350.0, 55.0, 0.0),
|
||||
"反应罐堆栈": Coordinate(1000.0, 55.0, 0.0),
|
||||
|
||||
}
|
||||
|
||||
for warehouse_name, warehouse in self.warehouses.items():
|
||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
157
unilabos/devices/workstation/post_process/opcua_huairou.json
Normal file
157
unilabos/devices/workstation/post_process/opcua_huairou.json
Normal file
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"register_node_list_from_csv_path": {
|
||||
"path": "opcua_nodes_huairou.csv"
|
||||
},
|
||||
"create_flow": [
|
||||
{
|
||||
"name": "trigger_grab_action",
|
||||
"description": "触发反应罐及原料罐抓取动作",
|
||||
"parameters": ["reaction_tank_number", "raw_tank_number"],
|
||||
"action": [
|
||||
{
|
||||
"init_function": {
|
||||
"func_name": "init_grab_params",
|
||||
"write_nodes": ["reaction_tank_number", "raw_tank_number"]
|
||||
},
|
||||
"start_function": {
|
||||
"func_name": "start_grab",
|
||||
"write_nodes": {"grab_trigger": true},
|
||||
"condition_nodes": ["grab_complete"],
|
||||
"stop_condition_expression": "grab_complete == True",
|
||||
"timeout_seconds": 999999.0
|
||||
},
|
||||
"stop_function": {
|
||||
"func_name": "stop_grab",
|
||||
"write_nodes": {"grab_trigger": false}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "trigger_post_processing",
|
||||
"description": "触发后处理动作",
|
||||
"parameters": ["atomization_fast_speed", "wash_slow_speed","injection_pump_suction_speed",
|
||||
"injection_pump_push_speed","raw_liquid_suction_count","first_wash_water_amount",
|
||||
"second_wash_water_amount","first_powder_mixing_time","second_powder_mixing_time",
|
||||
"first_powder_wash_count","second_powder_wash_count","initial_water_amount",
|
||||
"pre_filtration_mixing_time","atomization_pressure_kpa"],
|
||||
"action": [
|
||||
{
|
||||
"init_function": {
|
||||
"func_name": "init_post_processing_params",
|
||||
"write_nodes": ["atomization_fast_speed", "wash_slow_speed","injection_pump_suction_speed",
|
||||
"injection_pump_push_speed","raw_liquid_suction_count","first_wash_water_amount",
|
||||
"second_wash_water_amount","first_powder_mixing_time","second_powder_mixing_time",
|
||||
"first_powder_wash_count","second_powder_wash_count","initial_water_amount",
|
||||
"pre_filtration_mixing_time","atomization_pressure_kpa"]
|
||||
},
|
||||
"start_function": {
|
||||
"func_name": "start_post_processing",
|
||||
"write_nodes": {"post_process_trigger": true},
|
||||
"condition_nodes": ["post_process_complete"],
|
||||
"stop_condition_expression": "post_process_complete == True",
|
||||
"timeout_seconds": 999999.0
|
||||
},
|
||||
"stop_function": {
|
||||
"func_name": "stop_post_processing",
|
||||
"write_nodes": {"post_process_trigger": false}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "trigger_cleaning_action",
|
||||
"description": "触发清洗及管路吹气动作",
|
||||
"parameters": ["nmp_outer_wall_cleaning_injection", "nmp_outer_wall_cleaning_count","nmp_outer_wall_cleaning_wait_time",
|
||||
"nmp_outer_wall_cleaning_waste_time","nmp_inner_wall_cleaning_injection","nmp_inner_wall_cleaning_count",
|
||||
"nmp_pump_cleaning_suction_count",
|
||||
"nmp_inner_wall_cleaning_waste_time",
|
||||
"nmp_stirrer_cleaning_injection",
|
||||
"nmp_stirrer_cleaning_count",
|
||||
"nmp_stirrer_cleaning_wait_time",
|
||||
"nmp_stirrer_cleaning_waste_time",
|
||||
"water_outer_wall_cleaning_injection",
|
||||
"water_outer_wall_cleaning_count",
|
||||
"water_outer_wall_cleaning_wait_time",
|
||||
"water_outer_wall_cleaning_waste_time",
|
||||
"water_inner_wall_cleaning_injection",
|
||||
"water_inner_wall_cleaning_count",
|
||||
"water_pump_cleaning_suction_count",
|
||||
"water_inner_wall_cleaning_waste_time",
|
||||
"water_stirrer_cleaning_injection",
|
||||
"water_stirrer_cleaning_count",
|
||||
"water_stirrer_cleaning_wait_time",
|
||||
"water_stirrer_cleaning_waste_time",
|
||||
"acetone_outer_wall_cleaning_injection",
|
||||
"acetone_outer_wall_cleaning_count",
|
||||
"acetone_outer_wall_cleaning_wait_time",
|
||||
"acetone_outer_wall_cleaning_waste_time",
|
||||
"acetone_inner_wall_cleaning_injection",
|
||||
"acetone_inner_wall_cleaning_count",
|
||||
"acetone_pump_cleaning_suction_count",
|
||||
"acetone_inner_wall_cleaning_waste_time",
|
||||
"acetone_stirrer_cleaning_injection",
|
||||
"acetone_stirrer_cleaning_count",
|
||||
"acetone_stirrer_cleaning_wait_time",
|
||||
"acetone_stirrer_cleaning_waste_time",
|
||||
"pipe_blowing_time",
|
||||
"injection_pump_forward_empty_suction_count",
|
||||
"injection_pump_reverse_empty_suction_count",
|
||||
"filtration_liquid_selection"],
|
||||
"action": [
|
||||
{
|
||||
"init_function": {
|
||||
"func_name": "init_cleaning_params",
|
||||
"write_nodes": ["nmp_outer_wall_cleaning_injection", "nmp_outer_wall_cleaning_count","nmp_outer_wall_cleaning_wait_time",
|
||||
"nmp_outer_wall_cleaning_waste_time","nmp_inner_wall_cleaning_injection","nmp_inner_wall_cleaning_count",
|
||||
"nmp_pump_cleaning_suction_count",
|
||||
"nmp_inner_wall_cleaning_waste_time",
|
||||
"nmp_stirrer_cleaning_injection",
|
||||
"nmp_stirrer_cleaning_count",
|
||||
"nmp_stirrer_cleaning_wait_time",
|
||||
"nmp_stirrer_cleaning_waste_time",
|
||||
"water_outer_wall_cleaning_injection",
|
||||
"water_outer_wall_cleaning_count",
|
||||
"water_outer_wall_cleaning_wait_time",
|
||||
"water_outer_wall_cleaning_waste_time",
|
||||
"water_inner_wall_cleaning_injection",
|
||||
"water_inner_wall_cleaning_count",
|
||||
"water_pump_cleaning_suction_count",
|
||||
"water_inner_wall_cleaning_waste_time",
|
||||
"water_stirrer_cleaning_injection",
|
||||
"water_stirrer_cleaning_count",
|
||||
"water_stirrer_cleaning_wait_time",
|
||||
"water_stirrer_cleaning_waste_time",
|
||||
"acetone_outer_wall_cleaning_injection",
|
||||
"acetone_outer_wall_cleaning_count",
|
||||
"acetone_outer_wall_cleaning_wait_time",
|
||||
"acetone_outer_wall_cleaning_waste_time",
|
||||
"acetone_inner_wall_cleaning_injection",
|
||||
"acetone_inner_wall_cleaning_count",
|
||||
"acetone_pump_cleaning_suction_count",
|
||||
"acetone_inner_wall_cleaning_waste_time",
|
||||
"acetone_stirrer_cleaning_injection",
|
||||
"acetone_stirrer_cleaning_count",
|
||||
"acetone_stirrer_cleaning_wait_time",
|
||||
"acetone_stirrer_cleaning_waste_time",
|
||||
"pipe_blowing_time",
|
||||
"injection_pump_forward_empty_suction_count",
|
||||
"injection_pump_reverse_empty_suction_count",
|
||||
"filtration_liquid_selection"]
|
||||
},
|
||||
"start_function": {
|
||||
"func_name": "start_cleaning",
|
||||
"write_nodes": {"cleaning_and_pipe_blowing_trigger": true},
|
||||
"condition_nodes": ["cleaning_complete"],
|
||||
"stop_condition_expression": "cleaning_complete == True",
|
||||
"timeout_seconds": 999999.0
|
||||
},
|
||||
"stop_function": {
|
||||
"func_name": "stop_cleaning",
|
||||
"write_nodes": {"cleaning_and_pipe_blowing_trigger": false}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
Name,EnglishName,NodeType,DataType,NodeLanguage,NodeId
|
||||
原料罐号码,raw_tank_number,VARIABLE,INT16,Chinese,ns=4;s=OPC|原料罐号码
|
||||
反应罐号码,reaction_tank_number,VARIABLE,INT16,Chinese,ns=4;s=OPC|反应罐号码
|
||||
反应罐及原料罐抓取触发,grab_trigger,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|反应罐及原料罐抓取触发
|
||||
后处理动作触发,post_process_trigger,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|后处理动作触发
|
||||
搅拌桨雾化快速,atomization_fast_speed,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|搅拌桨雾化快速
|
||||
搅拌桨洗涤慢速,wash_slow_speed,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|搅拌桨洗涤慢速
|
||||
注射泵抽液速度,injection_pump_suction_speed,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵抽液速度
|
||||
注射泵推液速度,injection_pump_push_speed,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵推液速度
|
||||
抽原液次数,raw_liquid_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|抽原液次数
|
||||
第1次洗涤加水量,first_wash_water_amount,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|第1次洗涤加水量
|
||||
第2次洗涤加水量,second_wash_water_amount,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|第2次洗涤加水量
|
||||
第1次粉末搅拌时间,first_powder_mixing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|第1次粉末搅拌时间
|
||||
第2次粉末搅拌时间,second_powder_mixing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|第2次粉末搅拌时间
|
||||
第1次粉末洗涤次数,first_powder_wash_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|第1次粉末洗涤次数
|
||||
第2次粉末洗涤次数,second_powder_wash_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|第2次粉末洗涤次数
|
||||
最开始加水量,initial_water_amount,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|最开始加水量
|
||||
抽滤前搅拌时间,pre_filtration_mixing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|抽滤前搅拌时间
|
||||
雾化压力Kpa,atomization_pressure_kpa,VARIABLE,INT16,Chinese,ns=4;s=OPC|雾化压力Kpa
|
||||
清洗及管路吹气触发,cleaning_and_pipe_blowing_trigger,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|清洗及管路吹气触发
|
||||
废液桶满报警,waste_tank_full_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|废液桶满报警
|
||||
清水桶空报警,water_tank_empty_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|清水桶空报警
|
||||
NMP桶空报警,nmp_tank_empty_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|NMP桶空报警
|
||||
丙酮桶空报警,acetone_tank_empty_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|丙酮桶空报警
|
||||
门开报警,door_open_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|门开报警
|
||||
反应罐及原料罐抓取完成PLCtoPC,grab_complete,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|反应罐及原料罐抓取完成PLCtoPC
|
||||
后处理动作完成PLCtoPC,post_process_complete,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|后处理动作完成PLCtoPC
|
||||
清洗及管路吹气完成PLCtoPC,cleaning_complete,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|清洗及管路吹气完成PLCtoPC
|
||||
远程模式PLCtoPC,remote_mode,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|远程模式PLCtoPC
|
||||
设备准备就绪PLCtoPC,device_ready,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|设备准备就绪PLCtoPC
|
||||
NMP外壁清洗加注,nmp_outer_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|NMP外壁清洗加注
|
||||
NMP外壁清洗次数,nmp_outer_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP外壁清洗次数
|
||||
NMP外壁清洗等待时间,nmp_outer_wall_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP外壁清洗等待时间
|
||||
NMP外壁清洗抽废时间,nmp_outer_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP外壁清洗抽废时间
|
||||
NMP内壁清洗加注,nmp_inner_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|NMP内壁清洗加注
|
||||
NMP内壁清洗次数,nmp_inner_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP内壁清洗次数
|
||||
NMP泵清洗抽次数,nmp_pump_cleaning_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP泵清洗抽次数
|
||||
NMP内壁清洗抽废时间,nmp_inner_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP内壁清洗抽废时间
|
||||
NMP搅拌桨清洗加注,nmp_stirrer_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|NMP搅拌桨清洗加注
|
||||
NMP搅拌桨清洗次数,nmp_stirrer_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP搅拌桨清洗次数
|
||||
NMP搅拌桨清洗等待时间,nmp_stirrer_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP搅拌桨清洗等待时间
|
||||
NMP搅拌桨清洗抽废时间,nmp_stirrer_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP搅拌桨清洗抽废时间
|
||||
清水外壁清洗加注,water_outer_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|清水外壁清洗加注
|
||||
清水外壁清洗次数,water_outer_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水外壁清洗次数
|
||||
清水外壁清洗等待时间,water_outer_wall_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水外壁清洗等待时间
|
||||
清水外壁清洗抽废时间,water_outer_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水外壁清洗抽废时间
|
||||
清水内壁清洗加注,water_inner_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|清水内壁清洗加注
|
||||
清水内壁清洗次数,water_inner_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水内壁清洗次数
|
||||
清水泵清洗抽次数,water_pump_cleaning_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水泵清洗抽次数
|
||||
清水内壁清洗抽废时间,water_inner_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水内壁清洗抽废时间
|
||||
清水搅拌桨清洗加注,water_stirrer_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|清水搅拌桨清洗加注
|
||||
清水搅拌桨清洗次数,water_stirrer_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水搅拌桨清洗次数
|
||||
清水搅拌桨清洗等待时间,water_stirrer_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水搅拌桨清洗等待时间
|
||||
清水搅拌桨清洗抽废时间,water_stirrer_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水搅拌桨清洗抽废时间
|
||||
丙酮外壁清洗加注,acetone_outer_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|丙酮外壁清洗加注
|
||||
丙酮外壁清洗次数,acetone_outer_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮外壁清洗次数
|
||||
丙酮外壁清洗等待时间,acetone_outer_wall_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮外壁清洗等待时间
|
||||
丙酮外壁清洗抽废时间,acetone_outer_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮外壁清洗抽废时间
|
||||
丙酮内壁清洗加注,acetone_inner_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|丙酮内壁清洗加注
|
||||
丙酮内壁清洗次数,acetone_inner_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮内壁清洗次数
|
||||
丙酮泵清洗抽次数,acetone_pump_cleaning_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮泵清洗抽次数
|
||||
丙酮内壁清洗抽废时间,acetone_inner_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮内壁清洗抽废时间
|
||||
丙酮搅拌桨清洗加注,acetone_stirrer_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗加注
|
||||
丙酮搅拌桨清洗次数,acetone_stirrer_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗次数
|
||||
丙酮搅拌桨清洗等待时间,acetone_stirrer_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗等待时间
|
||||
丙酮搅拌桨清洗抽废时间,acetone_stirrer_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗抽废时间
|
||||
管道吹气时间,pipe_blowing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|管道吹气时间
|
||||
注射泵正向空抽次数,injection_pump_forward_empty_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵正向空抽次数
|
||||
注射泵反向空抽次数,injection_pump_reverse_empty_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵反向空抽次数
|
||||
抽滤液选择0水1丙酮,filtration_liquid_selection,VARIABLE,INT16,Chinese,ns=4;s=OPC|抽滤液选择0水1丙酮
|
||||
|
1781
unilabos/devices/workstation/post_process/post_process.py
Normal file
1781
unilabos/devices/workstation/post_process/post_process.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "post_process_station",
|
||||
"name": "post_process_station",
|
||||
"children": [
|
||||
"post_process_deck"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "post_process_station",
|
||||
"config": {
|
||||
"url": "opc.tcp://LAPTOP-AN6QGCSD:53530/OPCUA/SimulationServer",
|
||||
"config_path": "C:\\Users\\Roy\\Desktop\\DPLC\\Uni-Lab-OS\\unilabos\\devices\\workstation\\post_process\\opcua_huairou.json",
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "post_process_deck",
|
||||
"_resource_type": "unilabos.devices.workstation.post_process.decks:post_process_deck"
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "post_process_deck",
|
||||
"name": "post_process_deck",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "post_process_station",
|
||||
"type": "deck",
|
||||
"class": "post_process_deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "post_process_deck",
|
||||
"setup": true
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
from typing import Dict, Optional, List, Union
|
||||
from pylabrobot.resources import Coordinate
|
||||
from pylabrobot.resources.carrier import ResourceHolder, create_homogeneous_resources
|
||||
|
||||
from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR
|
||||
|
||||
|
||||
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
|
||||
def warehouse_factory(
|
||||
name: str,
|
||||
num_items_x: int = 1,
|
||||
num_items_y: int = 4,
|
||||
num_items_z: int = 4,
|
||||
dx: float = 137.0,
|
||||
dy: float = 96.0,
|
||||
dz: float = 120.0,
|
||||
item_dx: float = 10.0,
|
||||
item_dy: float = 10.0,
|
||||
item_dz: float = 10.0,
|
||||
resource_size_x: float = 127.0,
|
||||
resource_size_y: float = 86.0,
|
||||
resource_size_z: float = 25.0,
|
||||
removed_positions: Optional[List[int]] = None,
|
||||
empty: bool = False,
|
||||
category: str = "warehouse",
|
||||
model: Optional[str] = None,
|
||||
col_offset: int = 0, # 列起始偏移量,用于生成5-8等命名
|
||||
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
|
||||
):
|
||||
# 创建位置坐标
|
||||
locations = []
|
||||
|
||||
for layer in range(num_items_z): # 层
|
||||
for row in range(num_items_y): # 行
|
||||
for col in range(num_items_x): # 列
|
||||
# 计算位置
|
||||
x = dx + col * item_dx
|
||||
|
||||
# 根据 layout 决定 y 坐标计算
|
||||
if layout == "row-major":
|
||||
# 行优先:row=0(第1行) 应该显示在上方,y 值最小
|
||||
y = dy + row * item_dy
|
||||
else:
|
||||
# 列优先:保持原逻辑
|
||||
y = dy + (num_items_y - row - 1) * item_dy
|
||||
|
||||
z = dz + (num_items_z - layer - 1) * item_dz
|
||||
locations.append(Coordinate(x, y, z))
|
||||
|
||||
if removed_positions:
|
||||
locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
|
||||
|
||||
_sites = create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=locations,
|
||||
resource_size_x=resource_size_x,
|
||||
resource_size_y=resource_size_y,
|
||||
resource_size_z=resource_size_z,
|
||||
name_prefix=name,
|
||||
)
|
||||
|
||||
len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z)
|
||||
|
||||
# 🔑 修改:使用数字命名,最上面是4321,最下面是12,11,10,9
|
||||
# 命名顺序必须与坐标生成顺序一致:层 → 行 → 列
|
||||
keys = []
|
||||
for layer in range(num_items_z): # 遍历每一层
|
||||
for row in range(num_items_y): # 遍历每一行
|
||||
for col in range(num_items_x): # 遍历每一列
|
||||
# 倒序计算全局行号:row=0 应该对应 global_row=0(第1行:4321)
|
||||
# row=1 应该对应 global_row=1(第2行:8765)
|
||||
# row=2 应该对应 global_row=2(第3行:12,11,10,9)
|
||||
# 但前端显示时 row=2 在最上面,所以需要反转
|
||||
reversed_row = (num_items_y - 1 - row) # row=0→reversed_row=2, row=1→reversed_row=1, row=2→reversed_row=0
|
||||
global_row = layer * num_items_y + reversed_row
|
||||
|
||||
# 每行的最大数字 = (global_row + 1) * num_items_x + col_offset
|
||||
base_num = (global_row + 1) * num_items_x + col_offset
|
||||
|
||||
# 从右到左递减:4,3,2,1
|
||||
key = str(base_num - col)
|
||||
keys.append(key)
|
||||
|
||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
||||
|
||||
return WareHouse(
|
||||
name=name,
|
||||
size_x=dx + item_dx * num_items_x,
|
||||
size_y=dy + item_dy * num_items_y,
|
||||
size_z=dz + item_dz * num_items_z,
|
||||
num_items_x = num_items_x,
|
||||
num_items_y = num_items_y,
|
||||
num_items_z = num_items_z,
|
||||
ordering_layout=layout, # 传递排序方式到 ordering_layout
|
||||
sites=sites,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
|
||||
class WareHouse(ItemizedCarrier):
|
||||
"""堆栈载体类 - 可容纳16个板位的载体(4层x4行x1列)"""
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
num_items_x: int,
|
||||
num_items_y: int,
|
||||
num_items_z: int,
|
||||
layout: str = "x-y",
|
||||
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
||||
category: str = "warehouse",
|
||||
model: Optional[str] = None,
|
||||
ordering_layout: str = "col-major",
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
# ordered_items=ordered_items,
|
||||
# ordering=ordering,
|
||||
num_items_x=num_items_x,
|
||||
num_items_y=num_items_y,
|
||||
num_items_z=num_items_z,
|
||||
layout=layout,
|
||||
sites=sites,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
# 保存排序方式,供graphio.py的坐标映射使用
|
||||
# 使用独立属性避免与父类的layout冲突
|
||||
self.ordering_layout = ordering_layout
|
||||
|
||||
def serialize(self) -> dict:
|
||||
"""序列化时保存 ordering_layout 属性"""
|
||||
data = super().serialize()
|
||||
data['ordering_layout'] = self.ordering_layout
|
||||
return data
|
||||
|
||||
def get_site_by_layer_position(self, row: int, col: int, layer: int) -> ResourceHolder:
|
||||
if not (0 <= layer < 4 and 0 <= row < 4 and 0 <= col < 1):
|
||||
raise ValueError("无效的位置: layer={}, row={}, col={}".format(layer, row, col))
|
||||
|
||||
site_index = layer * 4 + row * 1 + col
|
||||
return self.sites[site_index]
|
||||
|
||||
def add_rack_to_position(self, row: int, col: int, layer: int, rack) -> None:
|
||||
site = self.get_site_by_layer_position(row, col, layer)
|
||||
site.assign_child_resource(rack)
|
||||
|
||||
def get_rack_at_position(self, row: int, col: int, layer: int):
|
||||
site = self.get_site_by_layer_position(row, col, layer)
|
||||
return site.resource
|
||||
38
unilabos/devices/workstation/post_process/warehouses.py
Normal file
38
unilabos/devices/workstation/post_process/warehouses.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from unilabos.devices.workstation.post_process.post_process_warehouse import WareHouse, warehouse_factory
|
||||
|
||||
|
||||
|
||||
# =================== Other ===================
|
||||
|
||||
|
||||
def post_process_warehouse_4x3x1(name: str) -> WareHouse:
|
||||
"""创建post_process 4x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def post_process_warehouse_4x3x1_2(name: str) -> WareHouse:
|
||||
"""已弃用:创建post_process 4x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=12.0,
|
||||
dy=12.0,
|
||||
dz=12.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
105
unilabos/registry/devices/cameraSII.yaml
Normal file
105
unilabos/registry/devices/cameraSII.yaml
Normal file
@@ -0,0 +1,105 @@
|
||||
cameracontroller_device:
|
||||
category:
|
||||
- cameraSII
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
config: null
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-stop:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: stop参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.cameraSII.cameraUSB:CameraController
|
||||
status_types:
|
||||
status: dict
|
||||
type: python
|
||||
config_info: []
|
||||
description: Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
audio_bitrate:
|
||||
default: 64k
|
||||
type: string
|
||||
audio_device:
|
||||
type: string
|
||||
fps:
|
||||
default: 30
|
||||
type: integer
|
||||
height:
|
||||
default: 720
|
||||
type: integer
|
||||
host_id:
|
||||
default: demo-host
|
||||
type: string
|
||||
rtmp_url:
|
||||
default: rtmp://srs.sciol.ac.cn:4499/live/camera-01
|
||||
type: string
|
||||
signal_backend_url:
|
||||
default: wss://sciol.ac.cn/api/realtime/signal/host
|
||||
type: string
|
||||
video_bitrate:
|
||||
default: 1500k
|
||||
type: string
|
||||
video_device:
|
||||
default: /dev/video0
|
||||
type: string
|
||||
webrtc_api:
|
||||
default: https://srs.sciol.ac.cn/rtc/v1/play/
|
||||
type: string
|
||||
webrtc_stream_url:
|
||||
default: webrtc://srs.sciol.ac.cn:4500/live/camera-01
|
||||
type: string
|
||||
width:
|
||||
default: 1280
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
status:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
@@ -1,73 +1,40 @@
|
||||
neware_battery_test_system:
|
||||
category:
|
||||
- neware_battery_test_system
|
||||
- neware
|
||||
- battery_test
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-post_init:
|
||||
debug_resource_names:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
ros_node: null
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
description: 调试方法:显示所有资源的实际名称
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
ros_node:
|
||||
return_info:
|
||||
description: 资源调试信息
|
||||
type: string
|
||||
success:
|
||||
description: 是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- ros_node
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: post_init参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-print_status_summary:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: print_status_summary参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-test_connection:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: test_connection参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
export_status_json:
|
||||
@@ -145,29 +112,32 @@ neware_battery_test_system:
|
||||
goal:
|
||||
plate_num: plate_num
|
||||
goal_default:
|
||||
plate_num: 1
|
||||
plate_num: null
|
||||
handles: {}
|
||||
result:
|
||||
plate_data: plate_data
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: 获取指定盘(1或2)的电池状态信息
|
||||
description: 获取指定盘或所有盘的状态信息
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
plate_num:
|
||||
description: 盘号 (1 或 2)
|
||||
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
|
||||
maximum: 2
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- plate_num
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
plate_data:
|
||||
description: 盘状态数据(单盘或所有盘)
|
||||
type: object
|
||||
return_info:
|
||||
description: 盘状态信息JSON格式
|
||||
description: 操作结果信息
|
||||
type: string
|
||||
success:
|
||||
description: 查询是否成功
|
||||
@@ -175,6 +145,7 @@ neware_battery_test_system:
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
- plate_data
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
@@ -219,7 +190,9 @@ neware_battery_test_system:
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
@@ -252,6 +225,56 @@ neware_battery_test_system:
|
||||
title: StrSingleInput
|
||||
type: object
|
||||
type: StrSingleInput
|
||||
submit_from_csv:
|
||||
feedback: {}
|
||||
goal:
|
||||
csv_path: string
|
||||
output_dir: string
|
||||
goal_default:
|
||||
csv_path: ''
|
||||
output_dir: .
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
submitted_count: submitted_count
|
||||
success: success
|
||||
schema:
|
||||
description: 从CSV文件批量提交Neware测试任务
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
csv_path:
|
||||
description: 输入CSV文件的绝对路径
|
||||
type: string
|
||||
output_dir:
|
||||
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
||||
type: string
|
||||
required:
|
||||
- csv_path
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 执行结果详细信息
|
||||
type: string
|
||||
submitted_count:
|
||||
description: 成功提交的任务数量
|
||||
type: integer
|
||||
success:
|
||||
description: 是否成功
|
||||
type: boolean
|
||||
total_count:
|
||||
description: CSV文件中的总行数
|
||||
type: integer
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
test_connection_action:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -284,30 +307,135 @@ neware_battery_test_system:
|
||||
- goal
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.battery.neware_battery_test_system:NewareBatteryTestSystem
|
||||
upload_backup_to_oss:
|
||||
feedback: {}
|
||||
goal:
|
||||
backup_dir: backup_dir
|
||||
file_pattern: file_pattern
|
||||
oss_prefix: oss_prefix
|
||||
goal_default:
|
||||
backup_dir: null
|
||||
file_pattern: '*'
|
||||
oss_prefix: null
|
||||
handles:
|
||||
output:
|
||||
- data_key: uploaded_files
|
||||
data_source: executor
|
||||
data_type: array
|
||||
handler_key: uploaded_files
|
||||
io_type: sink
|
||||
label: Uploaded Files (with standard flow info)
|
||||
result:
|
||||
failed_files: failed_files
|
||||
return_info: return_info
|
||||
success: success
|
||||
total_count: total_count
|
||||
uploaded_count: uploaded_count
|
||||
schema:
|
||||
description: 上传备份文件到阿里云OSS
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
backup_dir:
|
||||
description: 备份目录路径(默认使用最近一次submit_from_csv的backup_dir)
|
||||
type: string
|
||||
file_pattern:
|
||||
default: '*'
|
||||
description: 文件通配符模式,例如 *.csv 或 Battery_*.nda
|
||||
type: string
|
||||
oss_prefix:
|
||||
description: OSS对象路径前缀(默认使用self.oss_prefix)
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
failed_files:
|
||||
description: 上传失败的文件名列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
return_info:
|
||||
description: 上传操作结果信息
|
||||
type: string
|
||||
success:
|
||||
description: 上传是否成功
|
||||
type: boolean
|
||||
total_count:
|
||||
description: 总文件数
|
||||
type: integer
|
||||
uploaded_count:
|
||||
description: 成功上传的文件数
|
||||
type: integer
|
||||
uploaded_files:
|
||||
description: 成功上传的文件详情列表
|
||||
items:
|
||||
properties:
|
||||
Battery_Code:
|
||||
description: 电池编码
|
||||
type: string
|
||||
Electrolyte_Code:
|
||||
description: 电解液编码
|
||||
type: string
|
||||
filename:
|
||||
description: 文件名
|
||||
type: string
|
||||
url:
|
||||
description: OSS下载链接
|
||||
type: string
|
||||
required:
|
||||
- filename
|
||||
- url
|
||||
- Battery_Code
|
||||
- Electrolyte_Code
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
- uploaded_count
|
||||
- total_count
|
||||
- failed_files
|
||||
- uploaded_files
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
|
||||
status_types:
|
||||
channel_status: dict
|
||||
connection_info: dict
|
||||
device_summary: dict
|
||||
plate_status: dict
|
||||
status: str
|
||||
total_channels: int
|
||||
type: python
|
||||
config_info: []
|
||||
description: 新威电池测试系统驱动,支持720个通道的电池测试状态监控和数据导出。通过TCP通信实现远程控制,包含完整的物料管理系统,支持2盘电池的状态映射和监控。
|
||||
description: 新威电池测试系统驱动,提供720个通道的电池测试状态监控、物料管理和CSV批量提交功能。支持TCP通信实现远程控制,包含完整的物料管理系统(2盘电池状态映射),以及从CSV文件批量提交测试任务的能力。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
devtype:
|
||||
default: '27'
|
||||
type: string
|
||||
ip:
|
||||
default: 127.0.0.1
|
||||
type: string
|
||||
machine_id:
|
||||
default: 1
|
||||
type: integer
|
||||
oss_prefix:
|
||||
default: neware_backup
|
||||
description: OSS对象路径前缀
|
||||
type: string
|
||||
oss_upload_enabled:
|
||||
default: false
|
||||
description: 是否启用OSS上传功能
|
||||
type: boolean
|
||||
port:
|
||||
default: 502
|
||||
type: integer
|
||||
size_x:
|
||||
default: 500.0
|
||||
@@ -319,6 +447,7 @@ neware_battery_test_system:
|
||||
default: 2000.0
|
||||
type: number
|
||||
timeout:
|
||||
default: 20
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
@@ -330,8 +459,6 @@ neware_battery_test_system:
|
||||
type: object
|
||||
device_summary:
|
||||
type: object
|
||||
plate_status:
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
total_channels:
|
||||
@@ -341,7 +468,6 @@ neware_battery_test_system:
|
||||
- channel_status
|
||||
- connection_info
|
||||
- total_channels
|
||||
- plate_status
|
||||
- device_summary
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
630
unilabos/registry/devices/post_process_station.yaml
Normal file
630
unilabos/registry/devices/post_process_station.yaml
Normal file
@@ -0,0 +1,630 @@
|
||||
post_process_station:
|
||||
category:
|
||||
- post_process_station
|
||||
class:
|
||||
action_value_mappings:
|
||||
disconnect:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: {}
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
read_node:
|
||||
feedback:
|
||||
result: result
|
||||
goal:
|
||||
command: node_name
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
trigger_cleaning_action:
|
||||
feedback: {}
|
||||
goal:
|
||||
acetone_inner_wall_cleaning_count: acetone_inner_wall_cleaning_count
|
||||
acetone_inner_wall_cleaning_injection: acetone_inner_wall_cleaning_injection
|
||||
acetone_inner_wall_cleaning_waste_time: acetone_inner_wall_cleaning_waste_time
|
||||
acetone_outer_wall_cleaning_count: acetone_outer_wall_cleaning_count
|
||||
acetone_outer_wall_cleaning_injection: acetone_outer_wall_cleaning_injection
|
||||
acetone_outer_wall_cleaning_wait_time: acetone_outer_wall_cleaning_wait_time
|
||||
acetone_outer_wall_cleaning_waste_time: acetone_outer_wall_cleaning_waste_time
|
||||
acetone_pump_cleaning_suction_count: acetone_pump_cleaning_suction_count
|
||||
acetone_stirrer_cleaning_count: acetone_stirrer_cleaning_count
|
||||
acetone_stirrer_cleaning_injection: acetone_stirrer_cleaning_injection
|
||||
acetone_stirrer_cleaning_wait_time: acetone_stirrer_cleaning_wait_time
|
||||
acetone_stirrer_cleaning_waste_time: acetone_stirrer_cleaning_waste_time
|
||||
filtration_liquid_selection: filtration_liquid_selection
|
||||
injection_pump_forward_empty_suction_count: injection_pump_forward_empty_suction_count
|
||||
injection_pump_reverse_empty_suction_count: injection_pump_reverse_empty_suction_count
|
||||
nmp_inner_wall_cleaning_count: nmp_inner_wall_cleaning_count
|
||||
nmp_inner_wall_cleaning_injection: nmp_inner_wall_cleaning_injection
|
||||
nmp_inner_wall_cleaning_waste_time: nmp_inner_wall_cleaning_waste_time
|
||||
nmp_outer_wall_cleaning_count: nmp_outer_wall_cleaning_count
|
||||
nmp_outer_wall_cleaning_injection: nmp_outer_wall_cleaning_injection
|
||||
nmp_outer_wall_cleaning_wait_time: nmp_outer_wall_cleaning_wait_time
|
||||
nmp_outer_wall_cleaning_waste_time: nmp_outer_wall_cleaning_waste_time
|
||||
nmp_pump_cleaning_suction_count: nmp_pump_cleaning_suction_count
|
||||
nmp_stirrer_cleaning_count: nmp_stirrer_cleaning_count
|
||||
nmp_stirrer_cleaning_injection: nmp_stirrer_cleaning_injection
|
||||
nmp_stirrer_cleaning_wait_time: nmp_stirrer_cleaning_wait_time
|
||||
nmp_stirrer_cleaning_waste_time: nmp_stirrer_cleaning_waste_time
|
||||
pipe_blowing_time: pipe_blowing_time
|
||||
water_inner_wall_cleaning_count: water_inner_wall_cleaning_count
|
||||
water_inner_wall_cleaning_injection: water_inner_wall_cleaning_injection
|
||||
water_inner_wall_cleaning_waste_time: water_inner_wall_cleaning_waste_time
|
||||
water_outer_wall_cleaning_count: water_outer_wall_cleaning_count
|
||||
water_outer_wall_cleaning_injection: water_outer_wall_cleaning_injection
|
||||
water_outer_wall_cleaning_wait_time: water_outer_wall_cleaning_wait_time
|
||||
water_outer_wall_cleaning_waste_time: water_outer_wall_cleaning_waste_time
|
||||
water_pump_cleaning_suction_count: water_pump_cleaning_suction_count
|
||||
water_stirrer_cleaning_count: water_stirrer_cleaning_count
|
||||
water_stirrer_cleaning_injection: water_stirrer_cleaning_injection
|
||||
water_stirrer_cleaning_wait_time: water_stirrer_cleaning_wait_time
|
||||
water_stirrer_cleaning_waste_time: water_stirrer_cleaning_waste_time
|
||||
goal_default:
|
||||
acetone_inner_wall_cleaning_count: 0
|
||||
acetone_inner_wall_cleaning_injection: 0.0
|
||||
acetone_inner_wall_cleaning_waste_time: 0
|
||||
acetone_outer_wall_cleaning_count: 0
|
||||
acetone_outer_wall_cleaning_injection: 0.0
|
||||
acetone_outer_wall_cleaning_wait_time: 0
|
||||
acetone_outer_wall_cleaning_waste_time: 0
|
||||
acetone_pump_cleaning_suction_count: 0
|
||||
acetone_stirrer_cleaning_count: 0
|
||||
acetone_stirrer_cleaning_injection: 0.0
|
||||
acetone_stirrer_cleaning_wait_time: 0
|
||||
acetone_stirrer_cleaning_waste_time: 0
|
||||
filtration_liquid_selection: 0
|
||||
injection_pump_forward_empty_suction_count: 0
|
||||
injection_pump_reverse_empty_suction_count: 0
|
||||
nmp_inner_wall_cleaning_count: 0
|
||||
nmp_inner_wall_cleaning_injection: 0.0
|
||||
nmp_inner_wall_cleaning_waste_time: 0
|
||||
nmp_outer_wall_cleaning_count: 0
|
||||
nmp_outer_wall_cleaning_injection: 0.0
|
||||
nmp_outer_wall_cleaning_wait_time: 0
|
||||
nmp_outer_wall_cleaning_waste_time: 0
|
||||
nmp_pump_cleaning_suction_count: 0
|
||||
nmp_stirrer_cleaning_count: 0
|
||||
nmp_stirrer_cleaning_injection: 0.0
|
||||
nmp_stirrer_cleaning_wait_time: 0
|
||||
nmp_stirrer_cleaning_waste_time: 0
|
||||
pipe_blowing_time: 0
|
||||
water_inner_wall_cleaning_count: 0
|
||||
water_inner_wall_cleaning_injection: 0.0
|
||||
water_inner_wall_cleaning_waste_time: 0
|
||||
water_outer_wall_cleaning_count: 0
|
||||
water_outer_wall_cleaning_injection: 0.0
|
||||
water_outer_wall_cleaning_wait_time: 0
|
||||
water_outer_wall_cleaning_waste_time: 0
|
||||
water_pump_cleaning_suction_count: 0
|
||||
water_stirrer_cleaning_count: 0
|
||||
water_stirrer_cleaning_injection: 0.0
|
||||
water_stirrer_cleaning_wait_time: 0
|
||||
water_stirrer_cleaning_waste_time: 0
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: PostProcessTriggerClean_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
acetone_inner_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_inner_wall_cleaning_injection:
|
||||
type: number
|
||||
acetone_inner_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_outer_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_outer_wall_cleaning_injection:
|
||||
type: number
|
||||
acetone_outer_wall_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_outer_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_pump_cleaning_suction_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_stirrer_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_stirrer_cleaning_injection:
|
||||
type: number
|
||||
acetone_stirrer_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_stirrer_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
filtration_liquid_selection:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
injection_pump_forward_empty_suction_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
injection_pump_reverse_empty_suction_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_inner_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_inner_wall_cleaning_injection:
|
||||
type: number
|
||||
nmp_inner_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_outer_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_outer_wall_cleaning_injection:
|
||||
type: number
|
||||
nmp_outer_wall_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_outer_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_pump_cleaning_suction_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_stirrer_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_stirrer_cleaning_injection:
|
||||
type: number
|
||||
nmp_stirrer_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_stirrer_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
pipe_blowing_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_inner_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_inner_wall_cleaning_injection:
|
||||
type: number
|
||||
water_inner_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_outer_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_outer_wall_cleaning_injection:
|
||||
type: number
|
||||
water_outer_wall_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_outer_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_pump_cleaning_suction_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_stirrer_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_stirrer_cleaning_injection:
|
||||
type: number
|
||||
water_stirrer_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_stirrer_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- nmp_outer_wall_cleaning_injection
|
||||
- nmp_outer_wall_cleaning_count
|
||||
- nmp_outer_wall_cleaning_wait_time
|
||||
- nmp_outer_wall_cleaning_waste_time
|
||||
- nmp_inner_wall_cleaning_injection
|
||||
- nmp_inner_wall_cleaning_count
|
||||
- nmp_pump_cleaning_suction_count
|
||||
- nmp_inner_wall_cleaning_waste_time
|
||||
- nmp_stirrer_cleaning_injection
|
||||
- nmp_stirrer_cleaning_count
|
||||
- nmp_stirrer_cleaning_wait_time
|
||||
- nmp_stirrer_cleaning_waste_time
|
||||
- water_outer_wall_cleaning_injection
|
||||
- water_outer_wall_cleaning_count
|
||||
- water_outer_wall_cleaning_wait_time
|
||||
- water_outer_wall_cleaning_waste_time
|
||||
- water_inner_wall_cleaning_injection
|
||||
- water_inner_wall_cleaning_count
|
||||
- water_pump_cleaning_suction_count
|
||||
- water_inner_wall_cleaning_waste_time
|
||||
- water_stirrer_cleaning_injection
|
||||
- water_stirrer_cleaning_count
|
||||
- water_stirrer_cleaning_wait_time
|
||||
- water_stirrer_cleaning_waste_time
|
||||
- acetone_outer_wall_cleaning_injection
|
||||
- acetone_outer_wall_cleaning_count
|
||||
- acetone_outer_wall_cleaning_wait_time
|
||||
- acetone_outer_wall_cleaning_waste_time
|
||||
- acetone_inner_wall_cleaning_injection
|
||||
- acetone_inner_wall_cleaning_count
|
||||
- acetone_pump_cleaning_suction_count
|
||||
- acetone_inner_wall_cleaning_waste_time
|
||||
- acetone_stirrer_cleaning_injection
|
||||
- acetone_stirrer_cleaning_count
|
||||
- acetone_stirrer_cleaning_wait_time
|
||||
- acetone_stirrer_cleaning_waste_time
|
||||
- pipe_blowing_time
|
||||
- injection_pump_forward_empty_suction_count
|
||||
- injection_pump_reverse_empty_suction_count
|
||||
- filtration_liquid_selection
|
||||
title: PostProcessTriggerClean_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: PostProcessTriggerClean_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: PostProcessTriggerClean
|
||||
type: object
|
||||
type: PostProcessTriggerClean
|
||||
trigger_grab_action:
|
||||
feedback: {}
|
||||
goal:
|
||||
raw_tank_number: raw_tank_number
|
||||
reaction_tank_number: reaction_tank_number
|
||||
goal_default:
|
||||
raw_tank_number: 0
|
||||
reaction_tank_number: 0
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: PostProcessGrab_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
raw_tank_number:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
reaction_tank_number:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- reaction_tank_number
|
||||
- raw_tank_number
|
||||
title: PostProcessGrab_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: PostProcessGrab_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: PostProcessGrab
|
||||
type: object
|
||||
type: PostProcessGrab
|
||||
trigger_post_processing:
|
||||
feedback: {}
|
||||
goal:
|
||||
atomization_fast_speed: atomization_fast_speed
|
||||
atomization_pressure_kpa: atomization_pressure_kpa
|
||||
first_powder_mixing_tim: first_powder_mixing_tim
|
||||
first_powder_wash_count: first_powder_wash_count
|
||||
first_wash_water_amount: first_wash_water_amount
|
||||
initial_water_amount: initial_water_amount
|
||||
injection_pump_push_speed: injection_pump_push_speed
|
||||
injection_pump_suction_speed: injection_pump_suction_speed
|
||||
pre_filtration_mixing_time: pre_filtration_mixing_time
|
||||
raw_liquid_suction_count: raw_liquid_suction_count
|
||||
second_powder_mixing_time: second_powder_mixing_time
|
||||
second_powder_wash_count: second_powder_wash_count
|
||||
second_wash_water_amount: second_wash_water_amount
|
||||
wash_slow_speed: wash_slow_speed
|
||||
goal_default:
|
||||
atomization_fast_speed: 0.0
|
||||
atomization_pressure_kpa: 0
|
||||
first_powder_mixing_tim: 0
|
||||
first_powder_wash_count: 0
|
||||
first_wash_water_amount: 0.0
|
||||
initial_water_amount: 0.0
|
||||
injection_pump_push_speed: 0
|
||||
injection_pump_suction_speed: 0
|
||||
pre_filtration_mixing_time: 0
|
||||
raw_liquid_suction_count: 0
|
||||
second_powder_mixing_time: 0
|
||||
second_powder_wash_count: 0
|
||||
second_wash_water_amount: 0.0
|
||||
wash_slow_speed: 0.0
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: PostProcessTriggerPostPro_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
atomization_fast_speed:
|
||||
type: number
|
||||
atomization_pressure_kpa:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
first_powder_mixing_tim:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
first_powder_wash_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
first_wash_water_amount:
|
||||
type: number
|
||||
initial_water_amount:
|
||||
type: number
|
||||
injection_pump_push_speed:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
injection_pump_suction_speed:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
pre_filtration_mixing_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
raw_liquid_suction_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
second_powder_mixing_time:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
second_powder_wash_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
second_wash_water_amount:
|
||||
type: number
|
||||
wash_slow_speed:
|
||||
type: number
|
||||
required:
|
||||
- atomization_fast_speed
|
||||
- wash_slow_speed
|
||||
- injection_pump_suction_speed
|
||||
- injection_pump_push_speed
|
||||
- raw_liquid_suction_count
|
||||
- first_wash_water_amount
|
||||
- second_wash_water_amount
|
||||
- first_powder_mixing_tim
|
||||
- second_powder_mixing_time
|
||||
- first_powder_wash_count
|
||||
- second_powder_wash_count
|
||||
- initial_water_amount
|
||||
- pre_filtration_mixing_time
|
||||
- atomization_pressure_kpa
|
||||
title: PostProcessTriggerPostPro_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: PostProcessTriggerPostPro_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: PostProcessTriggerPostPro
|
||||
type: object
|
||||
type: PostProcessTriggerPostPro
|
||||
write_node:
|
||||
feedback:
|
||||
result: result
|
||||
goal:
|
||||
command: json_input
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.workstation.post_process.post_process:OpcUaClient
|
||||
status_types:
|
||||
acetone_tank_empty_alarm: Bool
|
||||
atomization_fast_speed: Float64
|
||||
atomization_pressure_kpa: Int32
|
||||
cleaning_complete: Bool
|
||||
device_ready: Bool
|
||||
door_open_alarm: Bool
|
||||
grab_complete: Bool
|
||||
grab_trigger: Bool
|
||||
injection_pump_push_speed: Int32
|
||||
injection_pump_suction_speed: Int32
|
||||
nmp_tank_empty_alarm: Bool
|
||||
post_process_complete: Bool
|
||||
post_process_trigger: Bool
|
||||
raw_tank_number: Int32
|
||||
reaction_tank_number: Int32
|
||||
remote_mode: Bool
|
||||
wash_slow_speed: Float64
|
||||
waste_tank_full_alarm: Bool
|
||||
water_tank_empty_alarm: Bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: 后处理站
|
||||
handles: []
|
||||
icon: post_process_station.webp
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
@@ -237,6 +237,8 @@ class Registry:
|
||||
resource_info["category"] = [file.stem]
|
||||
elif file.stem not in resource_info["category"]:
|
||||
resource_info["category"].append(file.stem)
|
||||
elif not isinstance(resource_info.get("category"), list):
|
||||
resource_info["category"] = [resource_info["category"]]
|
||||
if "config_info" not in resource_info:
|
||||
resource_info["config_info"] = []
|
||||
if "icon" not in resource_info:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
POST_PROCESS_Raw_1BottleCarrier:
|
||||
category:
|
||||
- bottle_carriers
|
||||
class:
|
||||
module: unilabos.devices.workstation.post_process.bottle_carriers:POST_PROCESS_Raw_1BottleCarrier
|
||||
type: pylabrobot
|
||||
description: POST_PROCESS_Raw_1BottleCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
POST_PROCESS_Reaction_1BottleCarrier:
|
||||
category:
|
||||
- bottle_carriers
|
||||
class:
|
||||
module: unilabos.devices.workstation.post_process.bottle_carriers:POST_PROCESS_Reaction_1BottleCarrier
|
||||
type: pylabrobot
|
||||
description: POST_PROCESS_Reaction_1BottleCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
11
unilabos/registry/resources/post_process/bottles.yaml
Normal file
11
unilabos/registry/resources/post_process/bottles.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
POST_PROCESS_PolymerStation_Reagent_Bottle:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.devices.workstation.post_process.bottles:POST_PROCESS_PolymerStation_Reagent_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
|
||||
12
unilabos/registry/resources/post_process/deck.yaml
Normal file
12
unilabos/registry/resources/post_process/deck.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
post_process_deck:
|
||||
category:
|
||||
- post_process_deck
|
||||
class:
|
||||
module: unilabos.devices.workstation.post_process.decks:post_process_deck
|
||||
type: pylabrobot
|
||||
description: post_process_deck
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
108
unilabos/registry/resources/prcxi/plate_adapters.yaml
Normal file
108
unilabos/registry/resources/prcxi/plate_adapters.yaml
Normal file
@@ -0,0 +1,108 @@
|
||||
PRCXI_30mm_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_30mm_Adapter
|
||||
type: pylabrobot
|
||||
description: '30mm适配器 (Code: ZX-58-30)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Adapter
|
||||
type: pylabrobot
|
||||
description: '适配器 (Code: Fhh478)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Deep10_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Deep10_Adapter
|
||||
type: pylabrobot
|
||||
description: '10ul专用深孔板适配器 (Code: ZX-002-10)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Deep300_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Deep300_Adapter
|
||||
type: pylabrobot
|
||||
description: '300ul深孔板适配器 (Code: ZX-002-300)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_PCR_Adapter
|
||||
type: pylabrobot
|
||||
description: '全裙边 PCR适配器 (Code: ZX-58-0001)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Reservoir_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Reservoir_Adapter
|
||||
type: pylabrobot
|
||||
description: '储液槽 适配器 (Code: ZX-ADP-001)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Tip10_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Tip10_Adapter
|
||||
type: pylabrobot
|
||||
description: '吸头10ul 适配器 (Code: ZX-58-10)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Tip1250_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Tip1250_Adapter
|
||||
type: pylabrobot
|
||||
description: 'Tip头适配器 1250uL (Code: ZX-58-1250)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Tip300_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_Tip300_Adapter
|
||||
type: pylabrobot
|
||||
description: 'ZHONGXI 适配器 300uL (Code: ZX-58-300)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
@@ -1,10 +1,130 @@
|
||||
prcxi_96_wellplate_360ul_flat:
|
||||
category:
|
||||
- plates
|
||||
PRCXI_48_DeepWell:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_res:prcxi_96_wellplate_360ul_flat
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_48_DeepWell
|
||||
type: pylabrobot
|
||||
description: prcxi_96_wellplate_360ul_flat
|
||||
description: '48孔深孔板 (Code: 22)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_96_DeepWell:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_96_DeepWell
|
||||
type: pylabrobot
|
||||
description: '96深孔板 (Code: q2)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_AGenBio_4_troughplate:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_AGenBio_4_troughplate
|
||||
type: pylabrobot
|
||||
description: '4道储液槽 (Code: sdfrth654)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_BioER_96_wellplate:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_BioER_96_wellplate
|
||||
type: pylabrobot
|
||||
description: '2.2ml 深孔板 (Code: ZX-019-2.2)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_BioRad_384_wellplate:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_BioRad_384_wellplate
|
||||
type: pylabrobot
|
||||
description: '384板 (Code: q3)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_CellTreat_96_wellplate:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_CellTreat_96_wellplate
|
||||
type: pylabrobot
|
||||
description: '细菌培养皿 (Code: ZX-78-096)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Plate_200uL_nonskirted:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_PCR_Plate_200uL_nonskirted
|
||||
type: pylabrobot
|
||||
description: '0.2ml PCR 板 (Code: ZX-023-0.2)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Plate_200uL_semiskirted:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_PCR_Plate_200uL_semiskirted
|
||||
type: pylabrobot
|
||||
description: '0.2ml PCR 板 (Code: ZX-023-0.2)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Plate_200uL_skirted:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_PCR_Plate_200uL_skirted
|
||||
type: pylabrobot
|
||||
description: '0.2ml PCR 板 (Code: ZX-023-0.2)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_nest_12_troughplate:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_nest_12_troughplate
|
||||
type: pylabrobot
|
||||
description: '12道储液槽 (Code: 12道储液槽)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_nest_1_troughplate:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_nest_1_troughplate
|
||||
type: pylabrobot
|
||||
description: '储液槽 (Code: ZX-58-10000)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
|
||||
@@ -1,23 +1,70 @@
|
||||
prcxi_opentrons_96_tiprack_10ul:
|
||||
category:
|
||||
- tip_racks
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_res:prcxi_opentrons_96_tiprack_10ul
|
||||
type: pylabrobot
|
||||
description: prcxi_opentrons_96_tiprack_10ul
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
tip_adaptor_1250ul_2:
|
||||
category:
|
||||
PRCXI_1000uL_Tips:
|
||||
category:
|
||||
- prcxi
|
||||
- tip_racks
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_materials:tip_adaptor_1250ul
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_1000uL_Tips
|
||||
type: pylabrobot
|
||||
description: Tip头适配器 1250uL
|
||||
description: '1000μL Tip头 (Code: ZX-001-1000)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_10uL_Tips:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_10uL_Tips
|
||||
type: pylabrobot
|
||||
description: '10μL Tip头 (Code: ZX-001-10)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_10ul_eTips:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_10ul_eTips
|
||||
type: pylabrobot
|
||||
description: '10μL加长 Tip头 (Code: ZX-001-10+)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_1250uL_Tips:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_1250uL_Tips
|
||||
type: pylabrobot
|
||||
description: '1250μL Tip头 (Code: ZX-001-1250)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_200uL_Tips:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_200uL_Tips
|
||||
type: pylabrobot
|
||||
description: '200μL Tip头 (Code: ZX-001-200)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_300ul_Tips:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_300ul_Tips
|
||||
type: pylabrobot
|
||||
description: '300μL Tip头 (Code: ZX-001-300)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
prcxi_trash:
|
||||
category:
|
||||
- trash
|
||||
PRCXI_trash:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_res:prcxi_trash
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_trash
|
||||
type: pylabrobot
|
||||
description: prcxi_trash
|
||||
description: '废弃槽 (Code: q1)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
|
||||
12
unilabos/registry/resources/prcxi/tube_racks.yaml
Normal file
12
unilabos/registry/resources/prcxi/tube_racks.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
PRCXI_EP_Adapter:
|
||||
category:
|
||||
- prcxi
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.prcxi.prcxi_labware:PRCXI_EP_Adapter
|
||||
type: pylabrobot
|
||||
description: 'ep适配器 (Code: 1)'
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
@@ -284,10 +284,18 @@ def modify_to_backend_format(data: list[dict[str, Any]]) -> list[dict[str, Any]]
|
||||
edge["sourceHandle"] = port[source]
|
||||
elif "source_port" in edge:
|
||||
edge["sourceHandle"] = edge.pop("source_port")
|
||||
else:
|
||||
typ = edge.get("type")
|
||||
if typ == "communication":
|
||||
continue
|
||||
if target in port:
|
||||
edge["targetHandle"] = port[target]
|
||||
elif "target_port" in edge:
|
||||
edge["targetHandle"] = edge.pop("target_port")
|
||||
else:
|
||||
typ = edge.get("type")
|
||||
if typ == "communication":
|
||||
continue
|
||||
edge["id"] = f"reactflow__edge-{source}-{edge['sourceHandle']}-{target}-{edge['targetHandle']}"
|
||||
for key in ["source_port", "target_port"]:
|
||||
if key in edge:
|
||||
|
||||
@@ -4,7 +4,11 @@ def register():
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Deck
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Plate
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300PlateAdapter
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300TipRack
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Trash
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300TubeRack
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.devices.workstation.workstation_base import WorkStationContainer
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "device",
|
||||
"class": "syringepump.runze",
|
||||
"class": "syringe_pump_with_valve.runze.SY03B-T08",
|
||||
"position": {
|
||||
"x": 620.6111111111111,
|
||||
"y": 171,
|
||||
@@ -93,7 +93,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 430.4087301587302,
|
||||
"y": 428,
|
||||
@@ -117,7 +117,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 295.36944444444447,
|
||||
"y": 428,
|
||||
@@ -141,7 +141,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 165.36944444444444,
|
||||
"y": 428,
|
||||
@@ -165,7 +165,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 165.36944444444444,
|
||||
"y": 428,
|
||||
@@ -189,7 +189,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 35,
|
||||
"y": 428,
|
||||
@@ -213,7 +213,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 698.1111111111111,
|
||||
"y": 428,
|
||||
@@ -255,7 +255,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "device",
|
||||
"class": "syringepump.runze",
|
||||
"class": "syringe_pump_with_valve.runze.SY03B-T08",
|
||||
"position": {
|
||||
"x": 1195.611507936508,
|
||||
"y": 686,
|
||||
@@ -279,7 +279,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1587.703373015873,
|
||||
"y": 1172.5,
|
||||
@@ -299,7 +299,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "device",
|
||||
"class": "separator_controller",
|
||||
"class": "separator.homemade",
|
||||
"position": {
|
||||
"x": 1624.4027777777778,
|
||||
"y": 665.5,
|
||||
@@ -320,7 +320,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1614.404365079365,
|
||||
"y": 948,
|
||||
@@ -340,7 +340,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1915.7035714285714,
|
||||
"y": 665.5,
|
||||
@@ -360,7 +360,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1785.7035714285714,
|
||||
"y": 665.5,
|
||||
@@ -384,7 +384,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 2054.0650793650793,
|
||||
"y": 665.5,
|
||||
@@ -408,7 +408,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "device",
|
||||
"class": "syringepump.runze",
|
||||
"class": "syringe_pump_with_valve.runze.SY03B-T08",
|
||||
"position": {
|
||||
"x": 1630.6527777777778,
|
||||
"y": 448.5,
|
||||
@@ -432,7 +432,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "device",
|
||||
"class": "rotavap",
|
||||
"class": "rotavap.one",
|
||||
"position": {
|
||||
"x": 1339.7031746031746,
|
||||
"y": 968.5,
|
||||
@@ -453,7 +453,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1339.7031746031746,
|
||||
"y": 1152,
|
||||
@@ -473,7 +473,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 909.722619047619,
|
||||
"y": 948,
|
||||
@@ -493,7 +493,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 867.972619047619,
|
||||
"y": 1152,
|
||||
@@ -513,7 +513,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 742.722619047619,
|
||||
"y": 948,
|
||||
@@ -533,7 +533,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1206.722619047619,
|
||||
"y": 948,
|
||||
@@ -553,7 +553,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"class": "container",
|
||||
"position": {
|
||||
"x": 1148.222619047619,
|
||||
"y": 1152,
|
||||
@@ -573,7 +573,7 @@
|
||||
"children": [],
|
||||
"parent": "YugongStation",
|
||||
"type": "device",
|
||||
"class": "syringepump.runze",
|
||||
"class": "syringe_pump_with_valve.runze.SY03B-T08",
|
||||
"position": {
|
||||
"x": 1469.7031746031746,
|
||||
"y": 968.5,
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300TipRack",
|
||||
"size_x": 50,
|
||||
"size_y": 40,
|
||||
"size_z": 30,
|
||||
@@ -4248,7 +4248,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 40,
|
||||
"size_z": 30,
|
||||
@@ -9415,7 +9415,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 40,
|
||||
"size_z": 30,
|
||||
@@ -13389,7 +13389,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 40,
|
||||
"size_z": 30,
|
||||
@@ -17363,7 +17363,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300Plate",
|
||||
"size_x": 50,
|
||||
"size_y": 40,
|
||||
"size_z": 30,
|
||||
|
||||
@@ -111,7 +111,7 @@ def upload_workflow(
|
||||
|
||||
if result.get("code") == 0:
|
||||
data = result.get("data", {})
|
||||
print_status("工作流上传成功!", "success")
|
||||
print_status(f"工作流上传成功!{data}", "success")
|
||||
print_status(f" - UUID: {data.get('uuid', 'N/A')}", "info")
|
||||
print_status(f" - 名称: {data.get('name', 'N/A')}", "info")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user