Workshop bj (#99)

* Add LaiYu Liquid device integration and tests

Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included.

* feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档

refactor: 重新组织LaiYu_Liquid模块目录结构
docs: 添加SOPA移液器和步进电机控制指令文档
fix: 修正设备配置中的最大体积默认值
test: 新增工作台配置测试用例
chore: 删除过时的测试脚本和配置文件

* add

* 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用

- 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py
- 更新所有相关文件中的导入引用
- 保持代码功能不变,仅改善命名一致性
- 测试确认所有导入正常工作

* 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出

- 添加 LaiYuLiquidBackend 到导入列表
- 添加 LaiYuLiquidBackend 到 __all__ 导出列表
- 确保所有主要类都可以正确导入

* 修复大小写文件夹名字
This commit is contained in:
ZiWei
2025-10-12 22:54:38 +08:00
committed by GitHub
parent 7c7f1b31c5
commit b263a7e679
36 changed files with 15862 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
LaiYu液体处理设备测试模块
该模块包含LaiYu液体处理设备的测试用例
- test_deck_config.py: 工作台配置测试
作者: UniLab团队
版本: 2.0.0
"""
__all__ = []

View File

@@ -0,0 +1,315 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试脚本验证更新后的deck配置是否正常工作
"""
import sys
import os
import json
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, project_root)
def test_config_loading():
"""测试配置文件加载功能"""
print("=" * 50)
print("测试配置文件加载功能")
print("=" * 50)
try:
# 直接测试配置文件加载
config_path = os.path.join(os.path.dirname(__file__), "controllers", "deckconfig.json")
fallback_path = os.path.join(os.path.dirname(__file__), "config", "deck.json")
config = None
config_source = ""
if os.path.exists(config_path):
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
config_source = "config/deckconfig.json"
elif os.path.exists(fallback_path):
with open(fallback_path, 'r', encoding='utf-8') as f:
config = json.load(f)
config_source = "config/deck.json"
else:
print("❌ 配置文件不存在")
return False
print(f"✅ 配置文件加载成功: {config_source}")
print(f" - 甲板尺寸: {config.get('size_x', 'N/A')} x {config.get('size_y', 'N/A')} x {config.get('size_z', 'N/A')}")
print(f" - 子模块数量: {len(config.get('children', []))}")
# 检查各个模块是否存在
modules = config.get('children', [])
module_types = [module.get('type') for module in modules]
module_names = [module.get('name') for module in modules]
print(f" - 模块类型: {', '.join(set(filter(None, module_types)))}")
print(f" - 模块名称: {', '.join(filter(None, module_names))}")
return config
except Exception as e:
print(f"❌ 配置文件加载失败: {e}")
return None
def test_module_coordinates(config):
"""测试各模块的坐标信息"""
print("\n" + "=" * 50)
print("测试模块坐标信息")
print("=" * 50)
if not config:
print("❌ 配置为空,无法测试")
return False
modules = config.get('children', [])
for module in modules:
module_name = module.get('name', '未知模块')
module_type = module.get('type', '未知类型')
position = module.get('position', {})
size = module.get('size', {})
print(f"\n模块: {module_name} ({module_type})")
print(f" - 位置: ({position.get('x', 0)}, {position.get('y', 0)}, {position.get('z', 0)})")
print(f" - 尺寸: {size.get('x', 0)} x {size.get('y', 0)} x {size.get('z', 0)}")
# 检查孔位信息
wells = module.get('wells', [])
if wells:
print(f" - 孔位数量: {len(wells)}")
# 显示前几个和后几个孔位的坐标
sample_wells = wells[:3] + wells[-3:] if len(wells) > 6 else wells
for well in sample_wells:
well_id = well.get('id', '未知')
well_pos = well.get('position', {})
print(f" {well_id}: ({well_pos.get('x', 0)}, {well_pos.get('y', 0)}, {well_pos.get('z', 0)})")
else:
print(f" - 无孔位信息")
return True
def test_coordinate_ranges(config):
"""测试坐标范围的合理性"""
print("\n" + "=" * 50)
print("测试坐标范围合理性")
print("=" * 50)
if not config:
print("❌ 配置为空,无法测试")
return False
deck_size = {
'x': config.get('size_x', 340),
'y': config.get('size_y', 250),
'z': config.get('size_z', 160)
}
print(f"甲板尺寸: {deck_size['x']} x {deck_size['y']} x {deck_size['z']}")
modules = config.get('children', [])
all_coordinates = []
for module in modules:
module_name = module.get('name', '未知模块')
wells = module.get('wells', [])
for well in wells:
well_pos = well.get('position', {})
x, y, z = well_pos.get('x', 0), well_pos.get('y', 0), well_pos.get('z', 0)
all_coordinates.append((x, y, z, f"{module_name}:{well.get('id', '未知')}"))
if not all_coordinates:
print("❌ 没有找到任何坐标信息")
return False
# 计算坐标范围
x_coords = [coord[0] for coord in all_coordinates]
y_coords = [coord[1] for coord in all_coordinates]
z_coords = [coord[2] for coord in all_coordinates]
x_range = (min(x_coords), max(x_coords))
y_range = (min(y_coords), max(y_coords))
z_range = (min(z_coords), max(z_coords))
print(f"X坐标范围: {x_range[0]:.2f} ~ {x_range[1]:.2f}")
print(f"Y坐标范围: {y_range[0]:.2f} ~ {y_range[1]:.2f}")
print(f"Z坐标范围: {z_range[0]:.2f} ~ {z_range[1]:.2f}")
# 检查是否超出甲板范围
issues = []
if x_range[1] > deck_size['x']:
issues.append(f"X坐标超出甲板范围: {x_range[1]} > {deck_size['x']}")
if y_range[1] > deck_size['y']:
issues.append(f"Y坐标超出甲板范围: {y_range[1]} > {deck_size['y']}")
if z_range[1] > deck_size['z']:
issues.append(f"Z坐标超出甲板范围: {z_range[1]} > {deck_size['z']}")
if x_range[0] < 0:
issues.append(f"X坐标为负值: {x_range[0]}")
if y_range[0] < 0:
issues.append(f"Y坐标为负值: {y_range[0]}")
if z_range[0] < 0:
issues.append(f"Z坐标为负值: {z_range[0]}")
if issues:
print("⚠️ 发现坐标问题:")
for issue in issues:
print(f" - {issue}")
return False
else:
print("✅ 所有坐标都在合理范围内")
return True
def test_well_spacing(config):
"""测试孔位间距的一致性"""
print("\n" + "=" * 50)
print("测试孔位间距一致性")
print("=" * 50)
if not config:
print("❌ 配置为空,无法测试")
return False
modules = config.get('children', [])
for module in modules:
module_name = module.get('name', '未知模块')
module_type = module.get('type', '未知类型')
wells = module.get('wells', [])
if len(wells) < 2:
continue
print(f"\n模块: {module_name} ({module_type})")
# 计算相邻孔位的间距
spacings_x = []
spacings_y = []
# 按行列排序孔位
wells_by_row = {}
for well in wells:
well_id = well.get('id', '')
if len(well_id) >= 3: # 如A01格式
row = well_id[0]
col = int(well_id[1:])
if row not in wells_by_row:
wells_by_row[row] = {}
wells_by_row[row][col] = well
# 计算同行相邻孔位的X间距
for row, cols in wells_by_row.items():
sorted_cols = sorted(cols.keys())
for i in range(len(sorted_cols) - 1):
col1, col2 = sorted_cols[i], sorted_cols[i + 1]
if col2 == col1 + 1: # 相邻列
pos1 = cols[col1].get('position', {})
pos2 = cols[col2].get('position', {})
spacing = abs(pos2.get('x', 0) - pos1.get('x', 0))
spacings_x.append(spacing)
# 计算同列相邻孔位的Y间距
cols_by_row = {}
for well in wells:
well_id = well.get('id', '')
if len(well_id) >= 3:
row = ord(well_id[0]) - ord('A')
col = int(well_id[1:])
if col not in cols_by_row:
cols_by_row[col] = {}
cols_by_row[col][row] = well
for col, rows in cols_by_row.items():
sorted_rows = sorted(rows.keys())
for i in range(len(sorted_rows) - 1):
row1, row2 = sorted_rows[i], sorted_rows[i + 1]
if row2 == row1 + 1: # 相邻行
pos1 = rows[row1].get('position', {})
pos2 = rows[row2].get('position', {})
spacing = abs(pos2.get('y', 0) - pos1.get('y', 0))
spacings_y.append(spacing)
# 检查间距一致性
if spacings_x:
avg_x = sum(spacings_x) / len(spacings_x)
max_diff_x = max(abs(s - avg_x) for s in spacings_x)
print(f" - X方向平均间距: {avg_x:.2f}mm, 最大偏差: {max_diff_x:.2f}mm")
if spacings_y:
avg_y = sum(spacings_y) / len(spacings_y)
max_diff_y = max(abs(s - avg_y) for s in spacings_y)
print(f" - Y方向平均间距: {avg_y:.2f}mm, 最大偏差: {max_diff_y:.2f}mm")
return True
def main():
"""主测试函数"""
print("LaiYu液体处理设备配置测试")
print("测试时间:", os.popen('date').read().strip())
# 运行所有测试
tests = [
("配置文件加载", test_config_loading),
]
config = None
results = []
for test_name, test_func in tests:
try:
if test_name == "配置文件加载":
result = test_func()
config = result if result else None
results.append((test_name, bool(result)))
else:
result = test_func(config)
results.append((test_name, result))
except Exception as e:
print(f"❌ 测试 {test_name} 执行失败: {e}")
results.append((test_name, False))
# 如果配置加载成功,运行其他测试
if config:
additional_tests = [
("模块坐标信息", test_module_coordinates),
("坐标范围合理性", test_coordinate_ranges),
("孔位间距一致性", test_well_spacing)
]
for test_name, test_func in additional_tests:
try:
result = test_func(config)
results.append((test_name, result))
except Exception as e:
print(f"❌ 测试 {test_name} 执行失败: {e}")
results.append((test_name, False))
# 输出测试总结
print("\n" + "=" * 50)
print("测试总结")
print("=" * 50)
passed = sum(1 for _, result in results if result)
total = len(results)
for test_name, result in results:
status = "✅ 通过" if result else "❌ 失败"
print(f" {test_name}: {status}")
print(f"\n总计: {passed}/{total} 个测试通过")
if passed == total:
print("🎉 所有测试通过!配置更新成功。")
return True
else:
print("⚠️ 部分测试失败,需要进一步检查。")
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)