diff --git a/test/experiments/dispensing_station_bioyond.json b/test/experiments/dispensing_station_bioyond.json index b2f79c80..745e1289 100644 --- a/test/experiments/dispensing_station_bioyond.json +++ b/test/experiments/dispensing_station_bioyond.json @@ -8,7 +8,7 @@ ], "parent": null, "type": "device", - "class": "dispensing_station.bioyond", + "class": "workstation.bioyond_dispensing_station", "config": { "config": { "api_key": "DE9BDDA0", @@ -20,13 +20,6 @@ "_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck" } }, - "station_config": { - "station_type": "dispensing_station", - "enable_dispensing_station": true, - "enable_reaction_station": false, - "station_name": "DispensingStation_001", - "description": "Bioyond配液工作站" - }, "protocol_type": [] }, "data": {} @@ -57,4 +50,4 @@ "data": {} } ] -} +} \ No newline at end of file diff --git a/test/experiments/reaction_station_bioyond.json b/test/experiments/reaction_station_bioyond.json index 2a18d90a..f09aeb91 100644 --- a/test/experiments/reaction_station_bioyond.json +++ b/test/experiments/reaction_station_bioyond.json @@ -24,9 +24,13 @@ "Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a" }, "material_type_mappings": { - "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier", - "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier", - "样品板": "BIOYOND_PolymerStation_6VialCarrier" + "烧杯": ["BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"], + "试剂瓶": ["BIOYOND_PolymerStation_1BottleCarrier", ""], + "样品板": ["BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"], + "分装板": ["BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"], + "样品瓶": ["BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"], + "90%分装小瓶": ["BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"], + "10%分装小瓶": ["BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"] } }, "deck": { @@ -42,7 +46,6 @@ { "id": "Bioyond_Deck", "name": "Bioyond_Deck", - "sample_id": null, "children": [ ], "parent": "reaction_station_bioyond", diff --git a/unilabos/devices/workstation/bioyond_studio/dispensing_station.py b/unilabos/devices/workstation/bioyond_studio/dispensing_station.py index b1820d6c..11b011cc 100644 --- a/unilabos/devices/workstation/bioyond_studio/dispensing_station.py +++ b/unilabos/devices/workstation/bioyond_studio/dispensing_station.py @@ -6,8 +6,15 @@ from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstati class BioyondDispensingStation(BioyondWorkstation): - def __init__(self, config): - super().__init__(config) + def __init__( + self, + config, + # 桌子 + deck, + *args, + **kwargs, + ): + super().__init__(config, deck, *args, **kwargs) # self.config = config # self.api_key = config["api_key"] # self.host = config["api_host"] diff --git a/unilabos/devices/workstation/bioyond_studio/experiment.py b/unilabos/devices/workstation/bioyond_studio/experiment.py index ae3111b8..92e52b45 100644 --- a/unilabos/devices/workstation/bioyond_studio/experiment.py +++ b/unilabos/devices/workstation/bioyond_studio/experiment.py @@ -1,203 +1,205 @@ -# experiment_workflow.py """ 实验流程主程序 """ import json -from bioyond_rpc import BioyondV1RPC -from config import API_CONFIG, WORKFLOW_MAPPINGS +from reaction_station import BioyondReactionStation +from config import API_CONFIG, WORKFLOW_MAPPINGS, DECK_CONFIG, MATERIAL_TYPE_MAPPINGS def run_experiment(): """运行实验流程""" - + # 初始化Bioyond客户端 config = { **API_CONFIG, - "workflow_mappings": WORKFLOW_MAPPINGS + "workflow_mappings": WORKFLOW_MAPPINGS, + "material_type_mappings": MATERIAL_TYPE_MAPPINGS } - - Bioyond = BioyondV1RPC(config) - + + # 创建BioyondReactionStation实例,传入deck配置 + Bioyond = BioyondReactionStation( + config=config, + deck=DECK_CONFIG + ) + print("\n============= 多工作流参数测试(简化接口+材料缓存)=============") - + # 显示可用的材料名称(前20个) - available_materials = Bioyond.get_available_materials() + available_materials = Bioyond.hardware_interface.get_available_materials() print(f"可用材料名称(前20个): {available_materials[:20]}") print(f"总共有 {len(available_materials)} 个材料可用\n") - + # 1. 反应器放入 print("1. 添加反应器放入工作流,带参数...") Bioyond.reactor_taken_in( - assign_material_name="BTDA-DD", - cutoff="10000", + assign_material_name="BTDA-DD", + cutoff="10000", temperature="-10" ) - + # 2. 液体投料-烧杯 (第一个) print("2. 添加液体投料-烧杯,带参数...") Bioyond.liquid_feeding_beaker( - volume="34768.7", + volume="34768.7", assign_material_name="ODA", - time="0", - torque_variation="1", - titrationType="1", + time="0", + torque_variation="1", + titrationType="1", temperature=-10 ) - + # 3. 液体投料-烧杯 (第二个) print("3. 添加液体投料-烧杯,带参数...") Bioyond.liquid_feeding_beaker( - volume="34080.9", + volume="34080.9", assign_material_name="MPDA", - time="5", - torque_variation="2", - titrationType="1", + time="5", + torque_variation="2", + titrationType="1", temperature=0 ) - + # 4. 液体投料-小瓶非滴定 print("4. 添加液体投料-小瓶非滴定,带参数...") Bioyond.liquid_feeding_vials_non_titration( - volumeFormula="639.5", - assign_material_name="SIDA", - titration_type="1", - time="0", - torque_variation="1", + volumeFormula="639.5", + assign_material_name="SIDA", + titration_type="1", + time="0", + torque_variation="1", temperature=-10 ) - + # 5. 液体投料溶剂 print("5. 添加液体投料溶剂,带参数...") Bioyond.liquid_feeding_solvents( assign_material_name="NMP", - volume="19000", - titration_type="1", - time="5", - torque_variation="2", + volume="19000", + titration_type="1", + time="5", + torque_variation="2", temperature=-10 ) - + # 6-8. 固体进料小瓶 (三个) print("6. 添加固体进料小瓶,带参数...") Bioyond.solid_feeding_vials( - material_id="3", - time="180", + material_id="3", + time="180", torque_variation="2", - assign_material_name="BTDA-1", + assign_material_name="BTDA1", temperature=-10.00 ) - +#二杆,样品版90 print("7. 添加固体进料小瓶,带参数...") Bioyond.solid_feeding_vials( - material_id="3", - time="180", + material_id="3", + time="180", torque_variation="2", - assign_material_name="BTDA-2", + assign_material_name="BTDA2", temperature=25.00 ) - +#二杆,样品版90 print("8. 添加固体进料小瓶,带参数...") Bioyond.solid_feeding_vials( - material_id="3", - time="480", + material_id="3", + time="480", torque_variation="2", - assign_material_name="BTDA-3", + assign_material_name="BTDA3", temperature=25.00 ) - + # 液体投料滴定(第一个) print("9. 添加液体投料滴定,带参数...") # ODPA Bioyond.liquid_feeding_titration( - volume_formula="1000", + volume_formula="{{6-0-5}}+{{7-0-5}}+{{8-0-5}}", assign_material_name="BTDA-DD", - titration_type="1", - time="360", - torque_variation="2", + titration_type="1", + time="360", + torque_variation="2", temperature="25.00" ) - + # 液体投料滴定(第二个) print("10. 添加液体投料滴定,带参数...") # ODPA Bioyond.liquid_feeding_titration( - volume_formula="500", + volume_formula="500", assign_material_name="BTDA-DD", - titration_type="1", - time="360", - torque_variation="2", + titration_type="1", + time="360", + torque_variation="2", temperature="25.00" ) # 液体投料滴定(第三个) print("11. 添加液体投料滴定,带参数...") # ODPA Bioyond.liquid_feeding_titration( - volume_formula="500", + volume_formula="500", assign_material_name="BTDA-DD", - titration_type="1", - time="360", - torque_variation="2", + titration_type="1", + time="360", + torque_variation="2", temperature="25.00" ) - + print("12. 添加液体投料滴定,带参数...") # ODPA Bioyond.liquid_feeding_titration( - volume_formula="500", + volume_formula="500", assign_material_name="BTDA-DD", - titration_type="1", - time="360", - torque_variation="2", + titration_type="1", + time="360", + torque_variation="2", temperature="25.00" ) - + print("13. 添加液体投料滴定,带参数...") # ODPA Bioyond.liquid_feeding_titration( - volume_formula="500", + volume_formula="500", assign_material_name="BTDA-DD", - titration_type="1", - time="360", - torque_variation="2", + titration_type="1", + time="360", + torque_variation="2", temperature="25.00" ) - + print("14. 添加液体投料滴定,带参数...") # ODPA Bioyond.liquid_feeding_titration( - volume_formula="500", + volume_formula="500", assign_material_name="BTDA-DD", - titration_type="1", - time="360", - torque_variation="2", + titration_type="1", + time="360", + torque_variation="2", temperature="25.00" ) - - print("15. 添加液体投料溶剂,带参数...") Bioyond.liquid_feeding_solvents( assign_material_name="PGME", - volume="16894.6", - titration_type="1", - time="360", - torque_variation="2", + volume="16894.6", + titration_type="1", + time="360", + torque_variation="2", temperature=25.00 ) - + # 16. 反应器取出 print("16. 添加反应器取出工作流...") Bioyond.reactor_taken_out() - + # 显示当前工作流序列 sequence = Bioyond.get_workflow_sequence() print("\n当前工作流执行顺序:") print(sequence) - + # 执行process_and_execute_workflow,合并工作流并创建任务 print("\n4. 执行process_and_execute_workflow...") - + result = Bioyond.process_and_execute_workflow( - workflow_name="test3_86", - task_name="实验3_86" + workflow_name="test3_8", + task_name="实验3_8" ) - + # 显示执行结果 print("\n5. 执行结果:") if isinstance(result, str): @@ -220,16 +222,16 @@ def run_experiment(): print(f"- 任务结果: {result.get('task')}") else: print(f"任务创建失败: {result.get('error')}") - + # 可选:启动调度器 # Bioyond.scheduler_start() - + return Bioyond def prepare_materials(bioyond): """准备实验材料(可选)""" - + # 样品板材料数据定义 material_data_yp_1 = { "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e", @@ -288,7 +290,7 @@ def prepare_materials(bioyond): ], "Parameters": "{}" } - + material_data_yp_2 = { "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e", "name": "样品板-2", @@ -338,7 +340,7 @@ def prepare_materials(bioyond): ], "Parameters": "{}" } - + # 烧杯材料数据定义 beaker_materials = [ { @@ -377,12 +379,12 @@ def prepare_materials(bioyond): "parameters": "{\"DeviceMaterialType\":\"NMP\"}" } ] - + # 如果需要,可以在这里调用add_material方法添加材料 # 例如: # result = bioyond.add_material(json.dumps(material_data_yp_1)) # print(f"添加材料结果: {result}") - + return { "sample_plates": [material_data_yp_1, material_data_yp_2], "beakers": beaker_materials @@ -392,7 +394,7 @@ def prepare_materials(bioyond): if __name__ == "__main__": # 运行主实验流程 bioyond_client = run_experiment() - + # 可选:准备材料数据 # materials = prepare_materials(bioyond_client) # print(f"\n准备的材料数据: {materials}") diff --git a/unilabos/devices/workstation/bioyond_studio/reaction_station.py b/unilabos/devices/workstation/bioyond_studio/reaction_station.py index e35c657f..5bb8709c 100644 --- a/unilabos/devices/workstation/bioyond_studio/reaction_station.py +++ b/unilabos/devices/workstation/bioyond_studio/reaction_station.py @@ -1,30 +1,67 @@ import json - +from typing import List, Dict, Any from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation from unilabos.devices.workstation.bioyond_studio.config import ( - API_CONFIG, WORKFLOW_MAPPINGS, WORKFLOW_STEP_IDS, MATERIAL_TYPE_MAPPINGS, - STATION_TYPES, DEFAULT_STATION_CONFIG + WORKFLOW_STEP_IDS, + WORKFLOW_TO_SECTION_MAP ) class BioyondReactionStation(BioyondWorkstation): - def __init__(self, config: dict = None): - super().__init__(config) + """Bioyond反应站类 + + 继承自BioyondWorkstation,提供反应站特定的业务方法 + """ + + def __init__(self, config: dict = None, deck=None): + """初始化反应站 + + Args: + config: 配置字典,应包含workflow_mappings等配置 + deck: Deck对象 + """ + # 如果 deck 作为独立参数传入,使用它;否则尝试从 config 中提取 + if deck is None and config: + deck = config.get('deck') + + # 调试信息:检查传入的config + print(f"BioyondReactionStation初始化 - config包含workflow_mappings: {'workflow_mappings' in (config or {})}") + if config and 'workflow_mappings' in config: + print(f"workflow_mappings内容: {config['workflow_mappings']}") + + # 将 config 作为 bioyond_config 传递给父类 + super().__init__(bioyond_config=config, deck=deck) + + # 调试信息:检查初始化后的workflow_mappings + print(f"BioyondReactionStation初始化完成 - workflow_mappings: {self.workflow_mappings}") + print(f"workflow_mappings长度: {len(self.workflow_mappings)}") + + # ==================== 工作流方法 ==================== - # 工作流方法 def reactor_taken_out(self): """反应器取出""" - self.hardware_interface.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_out"}') + self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_out"}') reactor_taken_out_params = {"param_values": {}} - self.hardware_interface.pending_task_params.append(reactor_taken_out_params) + self.pending_task_params.append(reactor_taken_out_params) print(f"成功添加反应器取出工作流") - print(f"当前队列长度: {len(self.hardware_interface.pending_task_params)}") + print(f"当前队列长度: {len(self.pending_task_params)}") return json.dumps({"suc": True}) - def reactor_taken_in(self, assign_material_name: str, cutoff: str = "900000", temperature: float = -10.00): - """反应器放入""" + def reactor_taken_in( + self, + assign_material_name: str, + cutoff: str = "900000", + temperature: float = -10.00 + ): + """反应器放入 + + Args: + assign_material_name: 物料名称 + cutoff: 截止参数 + temperature: 温度 + """ self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_in"}') - material_id = self._get_material_id_by_name(assign_material_name) + material_id = self.hardware_interface._get_material_id_by_name(assign_material_name) if isinstance(temperature, str): temperature = float(temperature) @@ -45,11 +82,25 @@ class BioyondReactionStation(BioyondWorkstation): print(f"当前队列长度: {len(self.pending_task_params)}") return json.dumps({"suc": True}) - def solid_feeding_vials(self, material_id: str, time: str = "0", torque_variation: str = "1", - assign_material_name: str = None, temperature: float = 25.00): - """固体进料小瓶""" + def solid_feeding_vials( + self, + material_id: str, + time: str = "0", + torque_variation: str = "1", + assign_material_name: str = None, + temperature: float = 25.00 + ): + """固体进料小瓶 + + Args: + material_id: 物料ID + time: 时间 + torque_variation: 扭矩变化 + assign_material_name: 物料名称 + temperature: 温度 + """ self.append_to_workflow_sequence('{"web_workflow_name": "Solid_feeding_vials"}') - material_id_m = self._get_material_id_by_name(assign_material_name) + material_id_m = self.hardware_interface._get_material_id_by_name(assign_material_name) if isinstance(temperature, str): temperature = float(temperature) @@ -76,12 +127,27 @@ class BioyondReactionStation(BioyondWorkstation): print(f"当前队列长度: {len(self.pending_task_params)}") return json.dumps({"suc": True}) - def liquid_feeding_vials_non_titration(self, volumeFormula: str, assign_material_name: str, - titration_type: str = "1", time: str = "0", - torque_variation: str = "1", temperature: float = 25.00): - """液体进料小瓶(非滴定)""" + def liquid_feeding_vials_non_titration( + self, + volumeFormula: str, + assign_material_name: str, + titration_type: str = "1", + time: str = "0", + torque_variation: str = "1", + temperature: float = 25.00 + ): + """液体进料小瓶(非滴定) + + Args: + volumeFormula: 体积公式 + assign_material_name: 物料名称 + titration_type: 滴定类型 + time: 时间 + torque_variation: 扭矩变化 + temperature: 温度 + """ self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}') - material_id = self._get_material_id_by_name(assign_material_name) + material_id = self.hardware_interface._get_material_id_by_name(assign_material_name) if isinstance(temperature, str): temperature = float(temperature) @@ -109,11 +175,27 @@ class BioyondReactionStation(BioyondWorkstation): print(f"当前队列长度: {len(self.pending_task_params)}") return json.dumps({"suc": True}) - def liquid_feeding_solvents(self, assign_material_name: str, volume: str, titration_type: str = "1", - time: str = "360", torque_variation: str = "2", temperature: float = 25.00): - """液体进料溶剂""" + def liquid_feeding_solvents( + self, + assign_material_name: str, + volume: str, + titration_type: str = "1", + time: str = "360", + torque_variation: str = "2", + temperature: float = 25.00 + ): + """液体进料溶剂 + + Args: + assign_material_name: 物料名称 + volume: 体积 + titration_type: 滴定类型 + time: 时间 + torque_variation: 扭矩变化 + temperature: 温度 + """ self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_solvents"}') - material_id = self._get_material_id_by_name(assign_material_name) + material_id = self.hardware_interface._get_material_id_by_name(assign_material_name) if isinstance(temperature, str): temperature = float(temperature) @@ -141,11 +223,27 @@ class BioyondReactionStation(BioyondWorkstation): print(f"当前队列长度: {len(self.pending_task_params)}") return json.dumps({"suc": True}) - def liquid_feeding_titration(self, volume_formula: str, assign_material_name: str, titration_type: str = "1", - time: str = "90", torque_variation: int = 2, temperature: float = 25.00): - """液体进料(滴定)""" + def liquid_feeding_titration( + self, + volume_formula: str, + assign_material_name: str, + titration_type: str = "1", + time: str = "90", + torque_variation: int = 2, + temperature: float = 25.00 + ): + """液体进料(滴定) + + Args: + volume_formula: 体积公式 + assign_material_name: 物料名称 + titration_type: 滴定类型 + time: 时间 + torque_variation: 扭矩变化 + temperature: 温度 + """ self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}') - material_id = self._get_material_id_by_name(assign_material_name) + material_id = self.hardware_interface._get_material_id_by_name(assign_material_name) if isinstance(temperature, str): temperature = float(temperature) @@ -173,12 +271,27 @@ class BioyondReactionStation(BioyondWorkstation): print(f"当前队列长度: {len(self.pending_task_params)}") return json.dumps({"suc": True}) - def liquid_feeding_beaker(self, volume: str = "35000", assign_material_name: str = "BAPP", - time: str = "0", torque_variation: str = "1", titrationType: str = "1", - temperature: float = 25.00): - """液体进料烧杯""" + def liquid_feeding_beaker( + self, + volume: str = "35000", + assign_material_name: str = "BAPP", + time: str = "0", + torque_variation: str = "1", + titrationType: str = "1", + temperature: float = 25.00 + ): + """液体进料烧杯 + + Args: + volume: 体积 + assign_material_name: 物料名称 + time: 时间 + torque_variation: 扭矩变化 + titrationType: 滴定类型 + temperature: 温度 + """ self.append_to_workflow_sequence('{"web_workflow_name": "liquid_feeding_beaker"}') - material_id = self._get_material_id_by_name(assign_material_name) + material_id = self.hardware_interface._get_material_id_by_name(assign_material_name) if isinstance(temperature, str): temperature = float(temperature) @@ -204,4 +317,323 @@ class BioyondReactionStation(BioyondWorkstation): self.pending_task_params.append(params) print(f"成功添加液体进料烧杯参数: volume={volume}μL, material={assign_material_name}->ID:{material_id}") print(f"当前队列长度: {len(self.pending_task_params)}") - return json.dumps({"suc": True}) \ No newline at end of file + return json.dumps({"suc": True}) + + # ==================== 工作流管理方法 ==================== + + def get_workflow_sequence(self) -> List[str]: + """获取当前工作流执行顺序 + + Returns: + 工作流名称列表 + """ + id_to_name = {workflow_id: name for name, workflow_id in self.workflow_mappings.items()} + workflow_names = [] + for workflow_id in self.workflow_sequence: + workflow_names.append(id_to_name.get(workflow_id, workflow_id)) + return workflow_names + + def workflow_step_query(self, workflow_id: str) -> dict: + """查询工作流步骤参数 + + Args: + workflow_id: 工作流ID + + Returns: + 工作流步骤参数字典 + """ + return self.hardware_interface.workflow_step_query(workflow_id) + + def create_order(self, json_str: str) -> dict: + """创建订单 + + Args: + json_str: 订单参数的JSON字符串 + + Returns: + 创建结果 + """ + return self.hardware_interface.create_order(json_str) + + # ==================== 工作流执行核心方法 ==================== + + # 发布任务 + def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict: + web_workflow_list = self.get_workflow_sequence() + workflow_name = workflow_name + + pending_params_backup = self.pending_task_params.copy() + print(f"保存pending_task_params副本,共{len(pending_params_backup)}个参数") + + # 1. 处理网页工作流列表 + print(f"处理网页工作流列表: {web_workflow_list}") + web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list}) + workflows_result = self.process_web_workflows(web_workflow_json) + + if not workflows_result: + error_msg = "处理网页工作流列表失败" + print(error_msg) + result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "process_web_workflows"}) + return result + + # 2. 合并工作流序列 + print(f"合并工作流序列,名称: {workflow_name}") + merge_json = json.dumps({"name": workflow_name}) + merged_workflow = self.merge_sequence_workflow(merge_json) + print(f"合并工作流序列结果: {merged_workflow}") + + if not merged_workflow: + error_msg = "合并工作流序列失败" + print(error_msg) + result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "merge_sequence_workflow"}) + return result + + # 3. 合并所有参数并创建任务 + # 新API只返回状态信息,需要适配处理 + if isinstance(merged_workflow, dict) and merged_workflow.get("code") == 1: + # 新API返回格式:{code: 1, message: "", timestamp: 0} + # 使用传入的工作流名称和生成的临时ID + final_workflow_name = workflow_name + workflow_id = f"merged_{workflow_name}_{self.hardware_interface.get_current_time_iso8601().replace('-', '').replace('T', '').replace(':', '').replace('.', '')[:14]}" + print(f"新API合并成功,使用工作流创建任务: {final_workflow_name} (临时ID: {workflow_id})") + else: + # 旧API返回格式:包含详细工作流信息 + final_workflow_name = merged_workflow.get("name", workflow_name) + workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "") + print(f"旧API格式,使用工作流创建任务: {final_workflow_name} (ID: {workflow_id})") + + if not workflow_id: + error_msg = "无法获取工作流ID" + print(error_msg) + result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "get_workflow_id"}) + return result + + workflow_query_json = json.dumps({"workflow_id": workflow_id}) + workflow_params_structure = self.workflow_step_query(workflow_query_json) + + self.pending_task_params = pending_params_backup + print(f"恢复pending_task_params,共{len(self.pending_task_params)}个参数") + + param_values = self.generate_task_param_values(workflow_params_structure) + + task_params = [{ + "orderCode": f"BSO{self.hardware_interface.get_current_time_iso8601().replace('-', '').replace('T', '').replace(':', '').replace('.', '')[:14]}", + "orderName": f"实验-{self.hardware_interface.get_current_time_iso8601()[:10].replace('-', '')}", + "workFlowId": workflow_id, + "borderNumber": 1, + "paramValues": param_values, + "extendProperties": "" + }] + + task_json = json.dumps(task_params) + print(f"创建任务参数: {type(task_json)}") + result = self.create_order(task_json) + + if not result: + error_msg = "创建任务失败" + print(error_msg) + result = str({"success": False, "error": f"process_and_execute_workflow:{error_msg}", "method": "process_and_execute_workflow", "step": "create_order"}) + return result + + print(f"任务创建成功: {result}") + self.pending_task_params.clear() + print("已清空pending_task_params") + + return { + "success": True, + "workflow": {"name": final_workflow_name, "id": workflow_id}, + "task": result, + "method": "process_and_execute_workflow" + } + + def merge_sequence_workflow(self, json_str: str) -> dict: + """合并当前工作流序列 + + Args: + json_str: 包含name等参数的JSON字符串 + + Returns: + 合并结果 + """ + try: + data = json.loads(json_str) + name = data.get("name", "合并工作流") + step_parameters = data.get("stepParameters", {}) + variables = data.get("variables", {}) + except json.JSONDecodeError: + return {} + + if not self.workflow_sequence: + print("工作流序列为空,无法合并") + return {} + + # 将工作流ID列表转换为新API要求的格式 + workflows = [{"id": workflow_id} for workflow_id in self.workflow_sequence] + + # 构建新的API参数格式 + params = { + "name": name, + "workflows": workflows, + "stepParameters": step_parameters, + "variables": variables + } + + # 使用新的API接口 + response = self.hardware_interface.post( + url=f'{self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters', + params={ + "apiKey": self.hardware_interface.api_key, + "requestTime": self.hardware_interface.get_current_time_iso8601(), + "data": params, + }) + + if not response or response['code'] != 1: + return {} + return response.get("data", {}) + + def generate_task_param_values(self, workflow_params_structure: dict) -> dict: + """生成任务参数值 + + 根据工作流参数结构和待处理的任务参数,生成最终的任务参数值 + + Args: + workflow_params_structure: 工作流参数结构 + + Returns: + 任务参数值字典 + """ + if not workflow_params_structure: + print("workflow_params_structure为空") + return {} + + data = workflow_params_structure + + # 从pending_task_params中提取实际参数值,按DisplaySectionName和Key组织 + pending_params_by_section = {} + print(f"开始处理pending_task_params,共{len(self.pending_task_params)}个任务参数组") + + # 获取工作流执行顺序,用于按顺序匹配参数 + workflow_sequence = self.get_workflow_sequence() + print(f"工作流执行顺序: {workflow_sequence}") + + workflow_index = 0 + + # 遍历所有待处理的任务参数 + for i, task_param in enumerate(self.pending_task_params): + if 'param_values' in task_param: + print(f"处理第{i+1}个任务参数组,包含{len(task_param['param_values'])}个步骤") + + if workflow_index < len(workflow_sequence): + current_workflow = workflow_sequence[workflow_index] + section_name = WORKFLOW_TO_SECTION_MAP.get(current_workflow) + print(f" 匹配到工作流: {current_workflow} -> {section_name}") + workflow_index += 1 + else: + print(f" 警告: 参数组{i+1}超出了工作流序列范围") + continue + + if not section_name: + print(f" 警告: 工作流{current_workflow}没有对应的DisplaySectionName") + continue + + if section_name not in pending_params_by_section: + pending_params_by_section[section_name] = {} + + # 处理每个步骤的参数 + for step_id, param_list in task_param['param_values'].items(): + print(f" 步骤ID: {step_id},参数数量: {len(param_list)}") + + for param_item in param_list: + key = param_item.get('Key', '') + value = param_item.get('Value', '') + m = param_item.get('m', 0) + n = param_item.get('n', 0) + print(f" 参数: {key} = {value} (m={m}, n={n}) -> 分组到{section_name}") + + param_key = f"{section_name}.{key}" + if param_key not in pending_params_by_section[section_name]: + pending_params_by_section[section_name][param_key] = [] + + pending_params_by_section[section_name][param_key].append({ + 'value': value, + 'm': m, + 'n': n + }) + + print(f"pending_params_by_section构建完成,包含{len(pending_params_by_section)}个分组") + + # 收集所有参数,过滤TaskDisplayable为0的项 + filtered_params = [] + + for step_id, step_info in data.items(): + if isinstance(step_info, list): + for step_item in step_info: + param_list = step_item.get("parameterList", []) + for param in param_list: + if param.get("TaskDisplayable") == 0: + continue + + param_with_step = param.copy() + param_with_step['step_id'] = step_id + param_with_step['step_name'] = step_item.get("name", "") + param_with_step['step_m'] = step_item.get("m", 0) + param_with_step['step_n'] = step_item.get("n", 0) + filtered_params.append(param_with_step) + + # 按DisplaySectionIndex排序 + filtered_params.sort(key=lambda x: x.get('DisplaySectionIndex', 0)) + + # 生成参数映射 + param_mapping = {} + step_params = {} + for param in filtered_params: + step_id = param['step_id'] + if step_id not in step_params: + step_params[step_id] = [] + step_params[step_id].append(param) + + # 为每个步骤生成参数 + for step_id, params in step_params.items(): + param_list = [] + for param in params: + key = param.get('Key', '') + display_section_index = param.get('DisplaySectionIndex', 0) + step_m = param.get('step_m', 0) + step_n = param.get('step_n', 0) + + section_name = param.get('DisplaySectionName', '') + param_key = f"{section_name}.{key}" + + if section_name in pending_params_by_section and param_key in pending_params_by_section[section_name]: + pending_param_list = pending_params_by_section[section_name][param_key] + if pending_param_list: + pending_param = pending_param_list[0] + value = pending_param['value'] + m = step_m + n = step_n + print(f" 匹配成功: {section_name}.{key} = {value} (m={m}, n={n})") + pending_param_list.pop(0) + else: + value = "1" + m = step_m + n = step_n + print(f" 匹配失败: {section_name}.{key},参数列表为空,使用默认值 = {value}") + else: + value = "1" + m = display_section_index + n = step_n + print(f" 匹配失败: {section_name}.{key},使用默认值 = {value} (m={m}, n={n})") + + param_item = { + "m": m, + "n": n, + "key": key, + "value": str(value).strip() + } + param_list.append(param_item) + + if param_list: + param_mapping[step_id] = param_list + + print(f"生成任务参数值,包含 {len(param_mapping)} 个步骤") + return param_mapping \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index f415a363..910fdb3a 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -129,7 +129,6 @@ class BioyondWorkstation(WorkstationBase): self, bioyond_config: Optional[Dict[str, Any]] = None, deck: Optional[Any] = None, - station_config: Optional[Dict[str, Any]] = None, *args, **kwargs, ): @@ -152,9 +151,6 @@ class BioyondWorkstation(WorkstationBase): if isinstance(resource, WareHouse): self.deck.warehouses[resource.name] = resource - # 配置站点类型 - self._configure_station_type(station_config) - # 创建通信模块 self._create_communication_module(bioyond_config) self.resource_synchronizer = BioyondResourceSynchronizer(self) @@ -167,8 +163,6 @@ class BioyondWorkstation(WorkstationBase): self.workflow_mappings = {} self.workflow_sequence = [] self.pending_task_params = [] - self.material_cache = {} - self._load_material_cache() if "workflow_mappings" in bioyond_config: self._set_workflow_mappings(bioyond_config["workflow_mappings"]) @@ -325,10 +319,22 @@ class BioyondWorkstation(WorkstationBase): } def append_to_workflow_sequence(self, web_workflow_name: str) -> bool: - workflow_id = self._get_workflow(web_workflow_name) + # 检查是否为JSON格式的字符串 + actual_workflow_name = web_workflow_name + if web_workflow_name.startswith('{') and web_workflow_name.endswith('}'): + try: + data = json.loads(web_workflow_name) + actual_workflow_name = data.get("web_workflow_name", web_workflow_name) + print(f"解析JSON格式工作流名称: {web_workflow_name} -> {actual_workflow_name}") + except json.JSONDecodeError: + print(f"JSON解析失败,使用原始字符串: {web_workflow_name}") + + workflow_id = self._get_workflow(actual_workflow_name) if workflow_id: self.workflow_sequence.append(workflow_id) - print(f"添加工作流到执行顺序: {web_workflow_name} -> {workflow_id}") + print(f"添加工作流到执行顺序: {actual_workflow_name} -> {workflow_id}") + return True + return False def set_workflow_sequence(self, json_str: str) -> List[str]: try: diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index c6780001..a8cd6152 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -4,6 +4,7 @@ import json import os.path import traceback from typing import Union, Any, Dict, List, Tuple +import uuid import networkx as nx from pylabrobot.resources import ResourceHolder from unilabos_msgs.msg import Resource @@ -629,6 +630,7 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st {"name": material["name"], "class": className}, resource_type=ResourcePLR ) plr_material.code = material.get("code", "") and material.get("barCode", "") or "" + plr_material.unilabos_uuid = str(uuid.uuid4()) # 处理子物料(detail) if material.get("detail") and len(material["detail"]) > 0: