mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-14 19:55:11 +00:00
Compare commits
4 Commits
ffc583e9d5
...
c7c14d2332
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c14d2332 | ||
|
|
6fdd482649 | ||
|
|
d390236318 | ||
|
|
ed8ee29732 |
@@ -218,7 +218,7 @@ def main():
|
|||||||
|
|
||||||
if hasattr(BasicConfig, "log_level"):
|
if hasattr(BasicConfig, "log_level"):
|
||||||
logger.info(f"Log level set to '{BasicConfig.log_level}' from config file.")
|
logger.info(f"Log level set to '{BasicConfig.log_level}' from config file.")
|
||||||
configure_logger(loglevel=BasicConfig.log_level)
|
configure_logger(loglevel=BasicConfig.log_level, working_dir=working_dir)
|
||||||
|
|
||||||
if args_dict["addr"] == "test":
|
if args_dict["addr"] == "test":
|
||||||
print_status("使用测试环境地址", "info")
|
print_status("使用测试环境地址", "info")
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ class LiquidHandlerMiddleware(LiquidHandler):
|
|||||||
offsets: Optional[List[Coordinate]] = None,
|
offsets: Optional[List[Coordinate]] = None,
|
||||||
**backend_kwargs,
|
**backend_kwargs,
|
||||||
):
|
):
|
||||||
|
# 如果 use_channels 为 None,使用默认值(所有通道)
|
||||||
|
if use_channels is None:
|
||||||
|
use_channels = list(range(self.channel_num))
|
||||||
if not offsets or (isinstance(offsets, list) and len(offsets) != len(use_channels)):
|
if not offsets or (isinstance(offsets, list) and len(offsets) != len(use_channels)):
|
||||||
offsets = [Coordinate.zero()] * len(use_channels)
|
offsets = [Coordinate.zero()] * len(use_channels)
|
||||||
if self._simulator:
|
if self._simulator:
|
||||||
@@ -759,7 +762,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
blow_out_air_volume=current_dis_blow_out_air_volume,
|
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||||
spread=spread,
|
spread=spread,
|
||||||
)
|
)
|
||||||
if delays is not None:
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
await self.touch_tip(current_targets)
|
await self.touch_tip(current_targets)
|
||||||
await self.discard_tips()
|
await self.discard_tips()
|
||||||
@@ -833,17 +836,19 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
spread=spread,
|
spread=spread,
|
||||||
)
|
)
|
||||||
|
|
||||||
if delays is not None:
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
await self.mix(
|
# 只有在 mix_time 有效时才调用 mix
|
||||||
targets=[targets[_]],
|
if mix_time is not None and mix_time > 0:
|
||||||
mix_time=mix_time,
|
await self.mix(
|
||||||
mix_vol=mix_vol,
|
targets=[targets[_]],
|
||||||
offsets=offsets if offsets else None,
|
mix_time=mix_time,
|
||||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
mix_vol=mix_vol,
|
||||||
mix_rate=mix_rate if mix_rate else None,
|
offsets=offsets if offsets else None,
|
||||||
)
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
if delays is not None:
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
await self.touch_tip(targets[_])
|
await self.touch_tip(targets[_])
|
||||||
await self.discard_tips()
|
await self.discard_tips()
|
||||||
@@ -893,18 +898,20 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
blow_out_air_volume=current_dis_blow_out_air_volume,
|
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||||
spread=spread,
|
spread=spread,
|
||||||
)
|
)
|
||||||
if delays is not None:
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
|
||||||
await self.mix(
|
# 只有在 mix_time 有效时才调用 mix
|
||||||
targets=current_targets,
|
if mix_time is not None and mix_time > 0:
|
||||||
mix_time=mix_time,
|
await self.mix(
|
||||||
mix_vol=mix_vol,
|
targets=current_targets,
|
||||||
offsets=offsets if offsets else None,
|
mix_time=mix_time,
|
||||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
mix_vol=mix_vol,
|
||||||
mix_rate=mix_rate if mix_rate else None,
|
offsets=offsets if offsets else None,
|
||||||
)
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
if delays is not None:
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
await self.touch_tip(current_targets)
|
await self.touch_tip(current_targets)
|
||||||
await self.discard_tips()
|
await self.discard_tips()
|
||||||
@@ -942,60 +949,146 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
):
|
):
|
||||||
"""Transfer liquid from each *source* well/plate to the corresponding *target*.
|
"""Transfer liquid with automatic mode detection.
|
||||||
|
|
||||||
|
Supports three transfer modes:
|
||||||
|
1. One-to-many (1 source -> N targets): Distribute from one source to multiple targets
|
||||||
|
2. One-to-one (N sources -> N targets): Standard transfer, each source to corresponding target
|
||||||
|
3. Many-to-one (N sources -> 1 target): Combine multiple sources into one target
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
asp_vols, dis_vols
|
asp_vols, dis_vols
|
||||||
Single volume (µL) or list matching the number of transfers.
|
Single volume (µL) or list. Automatically expanded based on transfer mode.
|
||||||
sources, targets
|
sources, targets
|
||||||
Same‑length sequences of containers (wells or plates). In 96‑well mode
|
Containers (wells or plates). Length determines transfer mode:
|
||||||
each must contain exactly one plate.
|
- len(sources) == 1, len(targets) > 1: One-to-many mode
|
||||||
|
- len(sources) == len(targets): One-to-one mode
|
||||||
|
- len(sources) > 1, len(targets) == 1: Many-to-one mode
|
||||||
tip_racks
|
tip_racks
|
||||||
One or more TipRacks providing fresh tips.
|
One or more TipRacks providing fresh tips.
|
||||||
is_96_well
|
is_96_well
|
||||||
Set *True* to use the 96‑channel head.
|
Set *True* to use the 96‑channel head.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 确保 use_channels 有默认值
|
||||||
|
if use_channels is None:
|
||||||
|
use_channels = [0] if self.channel_num >= 1 else list(range(self.channel_num))
|
||||||
|
|
||||||
if is_96_well:
|
if is_96_well:
|
||||||
pass # This mode is not verified.
|
pass # This mode is not verified.
|
||||||
else:
|
else:
|
||||||
if len(asp_vols) != len(targets):
|
# 转换体积参数为列表
|
||||||
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
|
if isinstance(asp_vols, (int, float)):
|
||||||
|
asp_vols = [float(asp_vols)]
|
||||||
|
else:
|
||||||
|
asp_vols = [float(v) for v in asp_vols]
|
||||||
|
|
||||||
|
if isinstance(dis_vols, (int, float)):
|
||||||
|
dis_vols = [float(dis_vols)]
|
||||||
|
else:
|
||||||
|
dis_vols = [float(v) for v in dis_vols]
|
||||||
|
|
||||||
|
# 识别传输模式
|
||||||
|
num_sources = len(sources)
|
||||||
|
num_targets = len(targets)
|
||||||
|
|
||||||
|
if num_sources == 1 and num_targets > 1:
|
||||||
|
# 模式1: 一对多 (1 source -> N targets)
|
||||||
|
await self._transfer_one_to_many(
|
||||||
|
sources[0], targets, tip_racks, use_channels,
|
||||||
|
asp_vols, dis_vols, asp_flow_rates, dis_flow_rates,
|
||||||
|
offsets, touch_tip, liquid_height, blow_out_air_volume,
|
||||||
|
spread, mix_stage, mix_times, mix_vol, mix_rate,
|
||||||
|
mix_liquid_height, delays
|
||||||
|
)
|
||||||
|
elif num_sources > 1 and num_targets == 1:
|
||||||
|
# 模式2: 多对一 (N sources -> 1 target)
|
||||||
|
await self._transfer_many_to_one(
|
||||||
|
sources, targets[0], tip_racks, use_channels,
|
||||||
|
asp_vols, dis_vols, asp_flow_rates, dis_flow_rates,
|
||||||
|
offsets, touch_tip, liquid_height, blow_out_air_volume,
|
||||||
|
spread, mix_stage, mix_times, mix_vol, mix_rate,
|
||||||
|
mix_liquid_height, delays
|
||||||
|
)
|
||||||
|
elif num_sources == num_targets:
|
||||||
|
# 模式3: 一对一 (N sources -> N targets) - 原有逻辑
|
||||||
|
await self._transfer_one_to_one(
|
||||||
|
sources, targets, tip_racks, use_channels,
|
||||||
|
asp_vols, dis_vols, asp_flow_rates, dis_flow_rates,
|
||||||
|
offsets, touch_tip, liquid_height, blow_out_air_volume,
|
||||||
|
spread, mix_stage, mix_times, mix_vol, mix_rate,
|
||||||
|
mix_liquid_height, delays
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Unsupported transfer mode: {num_sources} sources -> {num_targets} targets. "
|
||||||
|
"Supported modes: 1->N, N->1, or N->N."
|
||||||
|
)
|
||||||
|
|
||||||
# 首先应该对任务分组,然后每次1个/8个进行操作处理
|
async def _transfer_one_to_one(
|
||||||
if len(use_channels) == 1:
|
self,
|
||||||
for _ in range(len(targets)):
|
sources: Sequence[Container],
|
||||||
tip = []
|
targets: Sequence[Container],
|
||||||
for ___ in range(len(use_channels)):
|
tip_racks: Sequence[TipRack],
|
||||||
tip.extend(next(self.current_tip))
|
use_channels: List[int],
|
||||||
await self.pick_up_tips(tip)
|
asp_vols: List[float],
|
||||||
|
dis_vols: List[float],
|
||||||
|
asp_flow_rates: Optional[List[Optional[float]]],
|
||||||
|
dis_flow_rates: Optional[List[Optional[float]]],
|
||||||
|
offsets: Optional[List[Coordinate]],
|
||||||
|
touch_tip: bool,
|
||||||
|
liquid_height: Optional[List[Optional[float]]],
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]],
|
||||||
|
spread: Literal["wide", "tight", "custom"],
|
||||||
|
mix_stage: Optional[Literal["none", "before", "after", "both"]],
|
||||||
|
mix_times: Optional[int],
|
||||||
|
mix_vol: Optional[int],
|
||||||
|
mix_rate: Optional[int],
|
||||||
|
mix_liquid_height: Optional[float],
|
||||||
|
delays: Optional[List[int]],
|
||||||
|
):
|
||||||
|
"""一对一传输模式:N sources -> N targets"""
|
||||||
|
# 验证参数长度
|
||||||
|
if len(asp_vols) != len(targets):
|
||||||
|
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
|
||||||
|
if len(dis_vols) != len(targets):
|
||||||
|
raise ValueError(f"Length of `dis_vols` {len(dis_vols)} must match `targets` {len(targets)}.")
|
||||||
|
if len(sources) != len(targets):
|
||||||
|
raise ValueError(f"Length of `sources` {len(sources)} must match `targets` {len(targets)}.")
|
||||||
|
|
||||||
await self.aspirate(
|
if len(use_channels) == 1:
|
||||||
resources=[sources[_]],
|
for _ in range(len(targets)):
|
||||||
vols=[asp_vols[_]],
|
tip = []
|
||||||
use_channels=use_channels,
|
for ___ in range(len(use_channels)):
|
||||||
flow_rates=[asp_flow_rates[0]] if asp_flow_rates else None,
|
tip.extend(next(self.current_tip))
|
||||||
offsets=[offsets[0]] if offsets else None,
|
await self.pick_up_tips(tip)
|
||||||
liquid_height=[liquid_height[0]] if liquid_height else None,
|
|
||||||
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
|
await self.aspirate(
|
||||||
spread=spread,
|
resources=[sources[_]],
|
||||||
)
|
vols=[asp_vols[_]],
|
||||||
if delays is not None:
|
use_channels=use_channels,
|
||||||
await self.custom_delay(seconds=delays[0])
|
flow_rates=[asp_flow_rates[_]] if asp_flow_rates and len(asp_flow_rates) > _ else None,
|
||||||
await self.dispense(
|
offsets=[offsets[_]] if offsets and len(offsets) > _ else None,
|
||||||
resources=[targets[_]],
|
liquid_height=[liquid_height[_]] if liquid_height and len(liquid_height) > _ else None,
|
||||||
vols=[dis_vols[_]],
|
blow_out_air_volume=[blow_out_air_volume[_]] if blow_out_air_volume and len(blow_out_air_volume) > _ else None,
|
||||||
use_channels=use_channels,
|
spread=spread,
|
||||||
flow_rates=[dis_flow_rates[1]] if dis_flow_rates else None,
|
)
|
||||||
offsets=[offsets[1]] if offsets else None,
|
if delays is not None:
|
||||||
blow_out_air_volume=[blow_out_air_volume[1]] if blow_out_air_volume else None,
|
await self.custom_delay(seconds=delays[0])
|
||||||
liquid_height=[liquid_height[1]] if liquid_height else None,
|
await self.dispense(
|
||||||
spread=spread,
|
resources=[targets[_]],
|
||||||
)
|
vols=[dis_vols[_]],
|
||||||
if delays is not None:
|
use_channels=use_channels,
|
||||||
await self.custom_delay(seconds=delays[1])
|
flow_rates=[dis_flow_rates[_]] if dis_flow_rates and len(dis_flow_rates) > _ else None,
|
||||||
|
offsets=[offsets[_]] if offsets and len(offsets) > _ else None,
|
||||||
|
blow_out_air_volume=[blow_out_air_volume[_]] if blow_out_air_volume and len(blow_out_air_volume) > _ else None,
|
||||||
|
liquid_height=[liquid_height[_]] if liquid_height and len(liquid_height) > _ else None,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
if mix_stage in ["after", "both"] and mix_times is not None and mix_times > 0:
|
||||||
await self.mix(
|
await self.mix(
|
||||||
targets=[targets[_]],
|
targets=[targets[_]],
|
||||||
mix_time=mix_times,
|
mix_time=mix_times,
|
||||||
@@ -1004,63 +1097,60 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
mix_rate=mix_rate if mix_rate else None,
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
)
|
)
|
||||||
if delays is not None:
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
await self.touch_tip(targets[_])
|
await self.touch_tip(targets[_])
|
||||||
await self.discard_tips()
|
await self.discard_tips(use_channels=use_channels)
|
||||||
|
|
||||||
elif len(use_channels) == 8:
|
elif len(use_channels) == 8:
|
||||||
# 对于8个的情况,需要判断此时任务是不是能被8通道移液站来成功处理
|
if len(targets) % 8 != 0:
|
||||||
if len(targets) % 8 != 0:
|
raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.")
|
||||||
raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.")
|
|
||||||
|
|
||||||
# 8个8个来取任务序列
|
for i in range(0, len(targets), 8):
|
||||||
|
tip = []
|
||||||
|
for _ in range(len(use_channels)):
|
||||||
|
tip.extend(next(self.current_tip))
|
||||||
|
await self.pick_up_tips(tip)
|
||||||
|
current_targets = targets[i:i + 8]
|
||||||
|
current_reagent_sources = sources[i:i + 8]
|
||||||
|
current_asp_vols = asp_vols[i:i + 8]
|
||||||
|
current_dis_vols = dis_vols[i:i + 8]
|
||||||
|
current_asp_flow_rates = asp_flow_rates[i:i + 8] if asp_flow_rates else None
|
||||||
|
current_asp_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||||
|
current_dis_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||||
|
current_asp_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||||
|
current_dis_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||||
|
current_asp_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||||
|
current_dis_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||||
|
current_dis_flow_rates = dis_flow_rates[i:i + 8] if dis_flow_rates else None
|
||||||
|
|
||||||
for i in range(0, len(targets), 8):
|
await self.aspirate(
|
||||||
# 取出8个任务
|
resources=current_reagent_sources,
|
||||||
tip = []
|
vols=current_asp_vols,
|
||||||
for _ in range(len(use_channels)):
|
use_channels=use_channels,
|
||||||
tip.extend(next(self.current_tip))
|
flow_rates=current_asp_flow_rates,
|
||||||
await self.pick_up_tips(tip)
|
offsets=current_asp_offset,
|
||||||
current_targets = targets[i:i + 8]
|
blow_out_air_volume=current_asp_blow_out_air_volume,
|
||||||
current_reagent_sources = sources[i:i + 8]
|
liquid_height=current_asp_liquid_height,
|
||||||
current_asp_vols = asp_vols[i:i + 8]
|
spread=spread,
|
||||||
current_dis_vols = dis_vols[i:i + 8]
|
)
|
||||||
current_asp_flow_rates = asp_flow_rates[i:i + 8]
|
|
||||||
current_asp_offset = offsets[i:i + 8] if offsets else [None] * 8
|
|
||||||
current_dis_offset = offsets[-i*8-8:len(offsets)-i*8] if offsets else [None] * 8
|
|
||||||
current_asp_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
|
||||||
current_dis_liquid_height = liquid_height[-i*8-8:len(liquid_height)-i*8] if liquid_height else [None] * 8
|
|
||||||
current_asp_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
|
||||||
current_dis_blow_out_air_volume = blow_out_air_volume[-i*8-8:len(blow_out_air_volume)-i*8] if blow_out_air_volume else [None] * 8
|
|
||||||
current_dis_flow_rates = dis_flow_rates[i:i + 8] if dis_flow_rates else [None] * 8
|
|
||||||
|
|
||||||
await self.aspirate(
|
if delays is not None:
|
||||||
resources=current_reagent_sources,
|
await self.custom_delay(seconds=delays[0])
|
||||||
vols=current_asp_vols,
|
await self.dispense(
|
||||||
use_channels=use_channels,
|
resources=current_targets,
|
||||||
flow_rates=current_asp_flow_rates,
|
vols=current_dis_vols,
|
||||||
offsets=current_asp_offset,
|
use_channels=use_channels,
|
||||||
blow_out_air_volume=current_asp_blow_out_air_volume,
|
flow_rates=current_dis_flow_rates,
|
||||||
liquid_height=current_asp_liquid_height,
|
offsets=current_dis_offset,
|
||||||
spread=spread,
|
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||||
)
|
liquid_height=current_dis_liquid_height,
|
||||||
|
spread=spread,
|
||||||
if delays is not None:
|
)
|
||||||
await self.custom_delay(seconds=delays[0])
|
if delays is not None and len(delays) > 1:
|
||||||
await self.dispense(
|
await self.custom_delay(seconds=delays[1])
|
||||||
resources=current_targets,
|
|
||||||
vols=current_dis_vols,
|
|
||||||
use_channels=use_channels,
|
|
||||||
flow_rates=current_dis_flow_rates,
|
|
||||||
offsets=current_dis_offset,
|
|
||||||
blow_out_air_volume=current_dis_blow_out_air_volume,
|
|
||||||
liquid_height=current_dis_liquid_height,
|
|
||||||
spread=spread,
|
|
||||||
)
|
|
||||||
if delays is not None:
|
|
||||||
await self.custom_delay(seconds=delays[1])
|
|
||||||
|
|
||||||
|
if mix_stage in ["after", "both"] and mix_times is not None and mix_times > 0:
|
||||||
await self.mix(
|
await self.mix(
|
||||||
targets=current_targets,
|
targets=current_targets,
|
||||||
mix_time=mix_times,
|
mix_time=mix_times,
|
||||||
@@ -1069,10 +1159,363 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
mix_rate=mix_rate if mix_rate else None,
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
)
|
)
|
||||||
if delays is not None:
|
if delays is not None and len(delays) > 1:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
await self.touch_tip(current_targets)
|
||||||
|
await self.discard_tips([0,1,2,3,4,5,6,7])
|
||||||
|
|
||||||
|
async def _transfer_one_to_many(
|
||||||
|
self,
|
||||||
|
source: Container,
|
||||||
|
targets: Sequence[Container],
|
||||||
|
tip_racks: Sequence[TipRack],
|
||||||
|
use_channels: List[int],
|
||||||
|
asp_vols: List[float],
|
||||||
|
dis_vols: List[float],
|
||||||
|
asp_flow_rates: Optional[List[Optional[float]]],
|
||||||
|
dis_flow_rates: Optional[List[Optional[float]]],
|
||||||
|
offsets: Optional[List[Coordinate]],
|
||||||
|
touch_tip: bool,
|
||||||
|
liquid_height: Optional[List[Optional[float]]],
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]],
|
||||||
|
spread: Literal["wide", "tight", "custom"],
|
||||||
|
mix_stage: Optional[Literal["none", "before", "after", "both"]],
|
||||||
|
mix_times: Optional[int],
|
||||||
|
mix_vol: Optional[int],
|
||||||
|
mix_rate: Optional[int],
|
||||||
|
mix_liquid_height: Optional[float],
|
||||||
|
delays: Optional[List[int]],
|
||||||
|
):
|
||||||
|
"""一对多传输模式:1 source -> N targets"""
|
||||||
|
# 验证和扩展体积参数
|
||||||
|
if len(asp_vols) == 1:
|
||||||
|
# 如果只提供一个吸液体积,计算总吸液体积(所有分液体积之和)
|
||||||
|
total_asp_vol = sum(dis_vols)
|
||||||
|
asp_vol = asp_vols[0] if asp_vols[0] >= total_asp_vol else total_asp_vol
|
||||||
|
else:
|
||||||
|
raise ValueError("For one-to-many mode, `asp_vols` should be a single value or list with one element.")
|
||||||
|
|
||||||
|
if len(dis_vols) != len(targets):
|
||||||
|
raise ValueError(f"Length of `dis_vols` {len(dis_vols)} must match `targets` {len(targets)}.")
|
||||||
|
|
||||||
|
if len(use_channels) == 1:
|
||||||
|
# 单通道模式:一次吸液,多次分液
|
||||||
|
tip = []
|
||||||
|
for _ in range(len(use_channels)):
|
||||||
|
tip.extend(next(self.current_tip))
|
||||||
|
await self.pick_up_tips(tip)
|
||||||
|
|
||||||
|
# 从源容器吸液(总体积)
|
||||||
|
await self.aspirate(
|
||||||
|
resources=[source],
|
||||||
|
vols=[asp_vol],
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=[asp_flow_rates[0]] if asp_flow_rates and len(asp_flow_rates) > 0 else None,
|
||||||
|
offsets=[offsets[0]] if offsets and len(offsets) > 0 else None,
|
||||||
|
liquid_height=[liquid_height[0]] if liquid_height and len(liquid_height) > 0 else None,
|
||||||
|
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume and len(blow_out_air_volume) > 0 else None,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None:
|
||||||
|
await self.custom_delay(seconds=delays[0])
|
||||||
|
|
||||||
|
# 分多次分液到不同的目标容器
|
||||||
|
for idx, target in enumerate(targets):
|
||||||
|
await self.dispense(
|
||||||
|
resources=[target],
|
||||||
|
vols=[dis_vols[idx]],
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=[dis_flow_rates[idx]] if dis_flow_rates and len(dis_flow_rates) > idx else None,
|
||||||
|
offsets=[offsets[idx]] if offsets and len(offsets) > idx else None,
|
||||||
|
blow_out_air_volume=[blow_out_air_volume[idx]] if blow_out_air_volume and len(blow_out_air_volume) > idx else None,
|
||||||
|
liquid_height=[liquid_height[idx]] if liquid_height and len(liquid_height) > idx else None,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
if mix_stage in ["after", "both"] and mix_times is not None and mix_times > 0:
|
||||||
|
await self.mix(
|
||||||
|
targets=[target],
|
||||||
|
mix_time=mix_times,
|
||||||
|
mix_vol=mix_vol,
|
||||||
|
offsets=offsets[idx:idx+1] if offsets else None,
|
||||||
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
|
if touch_tip:
|
||||||
|
await self.touch_tip([target])
|
||||||
|
|
||||||
|
await self.discard_tips(use_channels=use_channels)
|
||||||
|
|
||||||
|
elif len(use_channels) == 8:
|
||||||
|
# 8通道模式:需要确保目标数量是8的倍数
|
||||||
|
if len(targets) % 8 != 0:
|
||||||
|
raise ValueError(f"For 8-channel mode, number of targets {len(targets)} must be a multiple of 8.")
|
||||||
|
|
||||||
|
# 每次处理8个目标
|
||||||
|
for i in range(0, len(targets), 8):
|
||||||
|
tip = []
|
||||||
|
for _ in range(len(use_channels)):
|
||||||
|
tip.extend(next(self.current_tip))
|
||||||
|
await self.pick_up_tips(tip)
|
||||||
|
|
||||||
|
current_targets = targets[i:i + 8]
|
||||||
|
current_dis_vols = dis_vols[i:i + 8]
|
||||||
|
|
||||||
|
# 8个通道都从同一个源容器吸液,每个通道的吸液体积等于对应的分液体积
|
||||||
|
current_asp_flow_rates = asp_flow_rates[0:1] * 8 if asp_flow_rates and len(asp_flow_rates) > 0 else None
|
||||||
|
current_asp_offset = offsets[0:1] * 8 if offsets and len(offsets) > 0 else [None] * 8
|
||||||
|
current_asp_liquid_height = liquid_height[0:1] * 8 if liquid_height and len(liquid_height) > 0 else [None] * 8
|
||||||
|
current_asp_blow_out_air_volume = blow_out_air_volume[0:1] * 8 if blow_out_air_volume and len(blow_out_air_volume) > 0 else [None] * 8
|
||||||
|
|
||||||
|
# 从源容器吸液(8个通道都从同一个源,但每个通道的吸液体积不同)
|
||||||
|
await self.aspirate(
|
||||||
|
resources=[source] * 8, # 8个通道都从同一个源
|
||||||
|
vols=current_dis_vols, # 每个通道的吸液体积等于对应的分液体积
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=current_asp_flow_rates,
|
||||||
|
offsets=current_asp_offset,
|
||||||
|
liquid_height=current_asp_liquid_height,
|
||||||
|
blow_out_air_volume=current_asp_blow_out_air_volume,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None:
|
||||||
|
await self.custom_delay(seconds=delays[0])
|
||||||
|
|
||||||
|
# 分液到8个目标
|
||||||
|
current_dis_flow_rates = dis_flow_rates[i:i + 8] if dis_flow_rates else None
|
||||||
|
current_dis_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||||
|
current_dis_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||||
|
current_dis_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||||
|
|
||||||
|
await self.dispense(
|
||||||
|
resources=current_targets,
|
||||||
|
vols=current_dis_vols,
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=current_dis_flow_rates,
|
||||||
|
offsets=current_dis_offset,
|
||||||
|
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||||
|
liquid_height=current_dis_liquid_height,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
|
||||||
|
if mix_stage in ["after", "both"] and mix_times is not None and mix_times > 0:
|
||||||
|
await self.mix(
|
||||||
|
targets=current_targets,
|
||||||
|
mix_time=mix_times,
|
||||||
|
mix_vol=mix_vol,
|
||||||
|
offsets=offsets if offsets else None,
|
||||||
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if touch_tip:
|
||||||
await self.touch_tip(current_targets)
|
await self.touch_tip(current_targets)
|
||||||
await self.discard_tips([0,1,2,3,4,5,6,7])
|
|
||||||
|
await self.discard_tips([0,1,2,3,4,5,6,7])
|
||||||
|
|
||||||
|
async def _transfer_many_to_one(
|
||||||
|
self,
|
||||||
|
sources: Sequence[Container],
|
||||||
|
target: Container,
|
||||||
|
tip_racks: Sequence[TipRack],
|
||||||
|
use_channels: List[int],
|
||||||
|
asp_vols: List[float],
|
||||||
|
dis_vols: List[float],
|
||||||
|
asp_flow_rates: Optional[List[Optional[float]]],
|
||||||
|
dis_flow_rates: Optional[List[Optional[float]]],
|
||||||
|
offsets: Optional[List[Coordinate]],
|
||||||
|
touch_tip: bool,
|
||||||
|
liquid_height: Optional[List[Optional[float]]],
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]],
|
||||||
|
spread: Literal["wide", "tight", "custom"],
|
||||||
|
mix_stage: Optional[Literal["none", "before", "after", "both"]],
|
||||||
|
mix_times: Optional[int],
|
||||||
|
mix_vol: Optional[int],
|
||||||
|
mix_rate: Optional[int],
|
||||||
|
mix_liquid_height: Optional[float],
|
||||||
|
delays: Optional[List[int]],
|
||||||
|
):
|
||||||
|
"""多对一传输模式:N sources -> 1 target(汇总/混合)"""
|
||||||
|
# 验证和扩展体积参数
|
||||||
|
if len(asp_vols) != len(sources):
|
||||||
|
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `sources` {len(sources)}.")
|
||||||
|
|
||||||
|
# 支持两种模式:
|
||||||
|
# 1. dis_vols 为单个值:所有源汇总,使用总吸液体积或指定分液体积
|
||||||
|
# 2. dis_vols 长度等于 asp_vols:每个源按不同比例分液(按比例混合)
|
||||||
|
if len(dis_vols) == 1:
|
||||||
|
# 模式1:使用单个分液体积
|
||||||
|
total_dis_vol = sum(asp_vols)
|
||||||
|
dis_vol = dis_vols[0] if dis_vols[0] >= total_dis_vol else total_dis_vol
|
||||||
|
use_proportional_mixing = False
|
||||||
|
elif len(dis_vols) == len(asp_vols):
|
||||||
|
# 模式2:按不同比例混合
|
||||||
|
use_proportional_mixing = True
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"For many-to-one mode, `dis_vols` should be a single value or list with length {len(asp_vols)} "
|
||||||
|
f"(matching `asp_vols`). Got length {len(dis_vols)}."
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(use_channels) == 1:
|
||||||
|
# 单通道模式:多次吸液,一次分液
|
||||||
|
# 先混合前(如果需要)
|
||||||
|
if mix_stage in ["before", "both"] and mix_times is not None and mix_times > 0:
|
||||||
|
# 注意:在吸液前混合源容器通常不常见,这里跳过
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 从每个源容器吸液并分液到目标容器
|
||||||
|
for idx, source in enumerate(sources):
|
||||||
|
tip = []
|
||||||
|
for _ in range(len(use_channels)):
|
||||||
|
tip.extend(next(self.current_tip))
|
||||||
|
await self.pick_up_tips(tip)
|
||||||
|
|
||||||
|
await self.aspirate(
|
||||||
|
resources=[source],
|
||||||
|
vols=[asp_vols[idx]],
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=[asp_flow_rates[idx]] if asp_flow_rates and len(asp_flow_rates) > idx else None,
|
||||||
|
offsets=[offsets[idx]] if offsets and len(offsets) > idx else None,
|
||||||
|
liquid_height=[liquid_height[idx]] if liquid_height and len(liquid_height) > idx else None,
|
||||||
|
blow_out_air_volume=[blow_out_air_volume[idx]] if blow_out_air_volume and len(blow_out_air_volume) > idx else None,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None:
|
||||||
|
await self.custom_delay(seconds=delays[0])
|
||||||
|
|
||||||
|
# 分液到目标容器
|
||||||
|
if use_proportional_mixing:
|
||||||
|
# 按不同比例混合:使用对应的 dis_vols
|
||||||
|
dis_vol = dis_vols[idx]
|
||||||
|
dis_flow_rate = dis_flow_rates[idx] if dis_flow_rates and len(dis_flow_rates) > idx else None
|
||||||
|
dis_offset = offsets[idx] if offsets and len(offsets) > idx else None
|
||||||
|
dis_liquid_height = liquid_height[idx] if liquid_height and len(liquid_height) > idx else None
|
||||||
|
dis_blow_out = blow_out_air_volume[idx] if blow_out_air_volume and len(blow_out_air_volume) > idx else None
|
||||||
|
else:
|
||||||
|
# 标准模式:分液体积等于吸液体积
|
||||||
|
dis_vol = asp_vols[idx]
|
||||||
|
dis_flow_rate = dis_flow_rates[0] if dis_flow_rates and len(dis_flow_rates) > 0 else None
|
||||||
|
dis_offset = offsets[0] if offsets and len(offsets) > 0 else None
|
||||||
|
dis_liquid_height = liquid_height[0] if liquid_height and len(liquid_height) > 0 else None
|
||||||
|
dis_blow_out = blow_out_air_volume[0] if blow_out_air_volume and len(blow_out_air_volume) > 0 else None
|
||||||
|
|
||||||
|
await self.dispense(
|
||||||
|
resources=[target],
|
||||||
|
vols=[dis_vol],
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=[dis_flow_rate] if dis_flow_rate is not None else None,
|
||||||
|
offsets=[dis_offset] if dis_offset is not None else None,
|
||||||
|
blow_out_air_volume=[dis_blow_out] if dis_blow_out is not None else None,
|
||||||
|
liquid_height=[dis_liquid_height] if dis_liquid_height is not None else None,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
|
||||||
|
await self.discard_tips(use_channels=use_channels)
|
||||||
|
|
||||||
|
# 最后在目标容器中混合(如果需要)
|
||||||
|
if mix_stage in ["after", "both"] and mix_times is not None and mix_times > 0:
|
||||||
|
await self.mix(
|
||||||
|
targets=[target],
|
||||||
|
mix_time=mix_times,
|
||||||
|
mix_vol=mix_vol,
|
||||||
|
offsets=offsets[0:1] if offsets else None,
|
||||||
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if touch_tip:
|
||||||
|
await self.touch_tip([target])
|
||||||
|
|
||||||
|
elif len(use_channels) == 8:
|
||||||
|
# 8通道模式:需要确保源数量是8的倍数
|
||||||
|
if len(sources) % 8 != 0:
|
||||||
|
raise ValueError(f"For 8-channel mode, number of sources {len(sources)} must be a multiple of 8.")
|
||||||
|
|
||||||
|
# 每次处理8个源
|
||||||
|
for i in range(0, len(sources), 8):
|
||||||
|
tip = []
|
||||||
|
for _ in range(len(use_channels)):
|
||||||
|
tip.extend(next(self.current_tip))
|
||||||
|
await self.pick_up_tips(tip)
|
||||||
|
|
||||||
|
current_sources = sources[i:i + 8]
|
||||||
|
current_asp_vols = asp_vols[i:i + 8]
|
||||||
|
current_asp_flow_rates = asp_flow_rates[i:i + 8] if asp_flow_rates else None
|
||||||
|
current_asp_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||||
|
current_asp_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||||
|
current_asp_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||||
|
|
||||||
|
# 从8个源容器吸液
|
||||||
|
await self.aspirate(
|
||||||
|
resources=current_sources,
|
||||||
|
vols=current_asp_vols,
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=current_asp_flow_rates,
|
||||||
|
offsets=current_asp_offset,
|
||||||
|
blow_out_air_volume=current_asp_blow_out_air_volume,
|
||||||
|
liquid_height=current_asp_liquid_height,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None:
|
||||||
|
await self.custom_delay(seconds=delays[0])
|
||||||
|
|
||||||
|
# 分液到目标容器(每个通道分液到同一个目标)
|
||||||
|
if use_proportional_mixing:
|
||||||
|
# 按比例混合:使用对应的 dis_vols
|
||||||
|
current_dis_vols = dis_vols[i:i + 8]
|
||||||
|
current_dis_flow_rates = dis_flow_rates[i:i + 8] if dis_flow_rates else None
|
||||||
|
current_dis_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||||
|
current_dis_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||||
|
current_dis_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||||
|
else:
|
||||||
|
# 标准模式:每个通道分液体积等于其吸液体积
|
||||||
|
current_dis_vols = current_asp_vols
|
||||||
|
current_dis_flow_rates = dis_flow_rates[0:1] * 8 if dis_flow_rates else None
|
||||||
|
current_dis_offset = offsets[0:1] * 8 if offsets else [None] * 8
|
||||||
|
current_dis_liquid_height = liquid_height[0:1] * 8 if liquid_height else [None] * 8
|
||||||
|
current_dis_blow_out_air_volume = blow_out_air_volume[0:1] * 8 if blow_out_air_volume else [None] * 8
|
||||||
|
|
||||||
|
await self.dispense(
|
||||||
|
resources=[target] * 8, # 8个通道都分到同一个目标
|
||||||
|
vols=current_dis_vols,
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=current_dis_flow_rates,
|
||||||
|
offsets=current_dis_offset,
|
||||||
|
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||||
|
liquid_height=current_dis_liquid_height,
|
||||||
|
spread=spread,
|
||||||
|
)
|
||||||
|
|
||||||
|
if delays is not None and len(delays) > 1:
|
||||||
|
await self.custom_delay(seconds=delays[1])
|
||||||
|
|
||||||
|
await self.discard_tips([0,1,2,3,4,5,6,7])
|
||||||
|
|
||||||
|
# 最后在目标容器中混合(如果需要)
|
||||||
|
if mix_stage in ["after", "both"] and mix_times is not None and mix_times > 0:
|
||||||
|
await self.mix(
|
||||||
|
targets=[target],
|
||||||
|
mix_time=mix_times,
|
||||||
|
mix_vol=mix_vol,
|
||||||
|
offsets=offsets[0:1] if offsets else None,
|
||||||
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if touch_tip:
|
||||||
|
await self.touch_tip([target])
|
||||||
|
|
||||||
# except Exception as e:
|
# except Exception as e:
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class VirtualMultiwayValve:
|
|||||||
"""
|
"""
|
||||||
虚拟九通阀门 - 0号位连接transfer pump,1-8号位连接其他设备 🔄
|
虚拟九通阀门 - 0号位连接transfer pump,1-8号位连接其他设备 🔄
|
||||||
"""
|
"""
|
||||||
def __init__(self, port: str = "VIRTUAL", positions: int = 8):
|
def __init__(self, port: str = "VIRTUAL", positions: int = 8, **kwargs):
|
||||||
self.port = port
|
self.port = port
|
||||||
self.max_positions = positions # 1-8号位
|
self.max_positions = positions # 1-8号位
|
||||||
self.total_positions = positions + 1 # 0-8号位,共9个位置
|
self.total_positions = positions + 1 # 0-8号位,共9个位置
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class WorkstationBase(ABC):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
deck: Deck,
|
deck: Optional[Deck],
|
||||||
*args,
|
*args,
|
||||||
**kwargs, # 必须有kwargs
|
**kwargs, # 必须有kwargs
|
||||||
):
|
):
|
||||||
@@ -349,5 +349,5 @@ class WorkstationBase(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class ProtocolNode(WorkstationBase):
|
class ProtocolNode(WorkstationBase):
|
||||||
def __init__(self, deck: Optional[PLRResource], *args, **kwargs):
|
def __init__(self, protocol_type: List[str], deck: Optional[PLRResource], *args, **kwargs):
|
||||||
super().__init__(deck, *args, **kwargs)
|
super().__init__(deck, *args, **kwargs)
|
||||||
|
|||||||
@@ -6036,7 +6036,12 @@ workstation:
|
|||||||
properties:
|
properties:
|
||||||
deck:
|
deck:
|
||||||
type: string
|
type: string
|
||||||
|
protocol_type:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
|
- protocol_type
|
||||||
- deck
|
- deck
|
||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ container:
|
|||||||
category:
|
category:
|
||||||
- container
|
- container
|
||||||
class:
|
class:
|
||||||
module: unilabos.resources.container:RegularContainer
|
module: unilabos.resources.container:get_regular_container
|
||||||
type: pylabrobot
|
type: pylabrobot
|
||||||
description: regular organic container
|
description: regular organic container
|
||||||
handles:
|
handles:
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ class RegularContainer(Container):
|
|||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]):
|
def load_state(self, state: Dict[str, Any]):
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
|
||||||
|
def get_regular_container(name="container"):
|
||||||
|
r = RegularContainer(name=name)
|
||||||
|
r.category = "container"
|
||||||
|
return RegularContainer(name=name)
|
||||||
|
|
||||||
#
|
#
|
||||||
# class RegularContainer(object):
|
# class RegularContainer(object):
|
||||||
# # 第一个参数必须是id传入
|
# # 第一个参数必须是id传入
|
||||||
|
|||||||
@@ -1144,7 +1144,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
queried_resources = []
|
queried_resources = []
|
||||||
for resource_data in resource_inputs:
|
for resource_data in resource_inputs:
|
||||||
plr_resource = await self.get_resource_with_dir(
|
plr_resource = await self.get_resource_with_dir(
|
||||||
resource_ids=resource_data["id"], with_children=True
|
resource_id=resource_data["id"], with_children=True
|
||||||
)
|
)
|
||||||
queried_resources.append(plr_resource)
|
queried_resources.append(plr_resource)
|
||||||
|
|
||||||
|
|||||||
@@ -124,11 +124,14 @@ class ColoredFormatter(logging.Formatter):
|
|||||||
def _format_basic(self, record):
|
def _format_basic(self, record):
|
||||||
"""基本格式化,不包含颜色"""
|
"""基本格式化,不包含颜色"""
|
||||||
datetime_str = datetime.fromtimestamp(record.created).strftime("%y-%m-%d [%H:%M:%S,%f")[:-3] + "]"
|
datetime_str = datetime.fromtimestamp(record.created).strftime("%y-%m-%d [%H:%M:%S,%f")[:-3] + "]"
|
||||||
filename = os.path.basename(record.filename).rsplit(".", 1)[0] # 提取文件名(不含路径和扩展名)
|
filename = record.filename.replace(".py", "").split("\\")[-1] # 提取文件名(不含路径和扩展名)
|
||||||
|
if "/" in filename:
|
||||||
|
filename = filename.split("/")[-1]
|
||||||
module_path = f"{record.name}.{filename}"
|
module_path = f"{record.name}.{filename}"
|
||||||
func_line = f"{record.funcName}:{record.lineno}"
|
func_line = f"{record.funcName}:{record.lineno}"
|
||||||
|
right_info = f" [{func_line}] [{module_path}]"
|
||||||
|
|
||||||
formatted_message = f"{datetime_str} [{record.levelname}] [{module_path}] [{func_line}]: {record.getMessage()}"
|
formatted_message = f"{datetime_str} [{record.levelname}] {record.getMessage()}{right_info}"
|
||||||
|
|
||||||
if record.exc_info:
|
if record.exc_info:
|
||||||
exc_text = self.formatException(record.exc_info)
|
exc_text = self.formatException(record.exc_info)
|
||||||
@@ -150,7 +153,7 @@ class ColoredFormatter(logging.Formatter):
|
|||||||
|
|
||||||
|
|
||||||
# 配置日志处理器
|
# 配置日志处理器
|
||||||
def configure_logger(loglevel=None):
|
def configure_logger(loglevel=None, working_dir=None):
|
||||||
"""配置日志记录器
|
"""配置日志记录器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -191,9 +194,30 @@ def configure_logger(loglevel=None):
|
|||||||
|
|
||||||
# 添加处理器到根日志记录器
|
# 添加处理器到根日志记录器
|
||||||
root_logger.addHandler(console_handler)
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# 如果指定了工作目录,添加文件处理器
|
||||||
|
if working_dir is not None:
|
||||||
|
logs_dir = os.path.join(working_dir, "logs")
|
||||||
|
os.makedirs(logs_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 生成日志文件名:日期 时间.log
|
||||||
|
log_filename = datetime.now().strftime("%Y-%m-%d %H-%M-%S") + ".log"
|
||||||
|
log_filepath = os.path.join(logs_dir, log_filename)
|
||||||
|
|
||||||
|
# 创建文件处理器
|
||||||
|
file_handler = logging.FileHandler(log_filepath, encoding="utf-8")
|
||||||
|
file_handler.setLevel(root_logger.level)
|
||||||
|
|
||||||
|
# 使用不带颜色的格式化器
|
||||||
|
file_formatter = ColoredFormatter(use_colors=False)
|
||||||
|
file_handler.setFormatter(file_formatter)
|
||||||
|
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
|
||||||
logging.getLogger("asyncio").setLevel(logging.INFO)
|
logging.getLogger("asyncio").setLevel(logging.INFO)
|
||||||
logging.getLogger("urllib3").setLevel(logging.INFO)
|
logging.getLogger("urllib3").setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
# 配置日志系统
|
# 配置日志系统
|
||||||
configure_logger()
|
configure_logger()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user