Files
Uni-Lab-OS/unilabos/devices/hplc/AgilentHPLC.py
Junhan Chang a62a695812 Ready for open source (#47)
* Create app/main API

* create example device

* create ROS backend and example device SDK Wrapper

* Add ROS host and host starting from app.py

* Add gripper device and mock implementation

* add "status_types" & "action_types" to ROS device decorator

* add ActionServer debug example

* [bugfix] complete mock gripper example

* ROS Backend Host for Device action calling and Resource management

* add conda/mamba ENV file

* add host_node communication with app/main.py

* add action message value mappings and converters

* Update ilabos.yaml

* Update issue templates

* example devices.json and resources.json

* Fix Device wrapper to use async property and actions (#7)

* Fix Device wrapper to use async property and actions

* Resolve #1 : support async get methods and actions. Give new examples.

* add both sync/async GRBL controller SDK

* 2 call device actions from appmainpy api to ros hostpy (#8)

* feature: add job

* fix:node start

* feature:add get job status

* fix:get device

* clean

* Resolve #5 device connection diagram and workflow compilation support (#9)

* add syringe pump device and its compilation using device connection diagram

* add RunzeSyringePump real device with ROS2 example

* Prototype machine with 1 pump and 1 CNC

* add ROS2ProtocolNode and related functions

* add ilabos_msgs (to use PumpTransfer action)

* add example device connection graph

* refactor protocol_node code into separate file

* add ROS2SerialNode

* add SerialCommand srv in ilabos_msgs

* add pump_protocol example, and fix bugs

* [fix] serial service: avoid async service deadlock by directly call serial `send_command`

* use SendCmd instead of SingleJointPosition for valve control

* initialize device connection graph when server starts

* Fix #5: async workflow execution (#16)

* add rclpyx and protocol example for async-native workflow

* use async in ROS2ProtocolNode, and host initialization

* add examples of "ros-async" protocol implementation, and `run_in_event_loop` for using native async functions

* use "ros-async" in protocols and device nodes

* fix pump_protocol: push to 0.0 μL

* Envs, docs, and conda recipes (#19)

* update ENV: use python 3.11 and deprecate ros-humble-gazebo-ros

* add ilabos-msgs conda recipe

* Update ilabos.yaml

* fix recipe and env yaml

* Add sphinx docs

* add aichemeco

* add bioyong

* add bioyong

* Support XDL devices & protocols (#20)

* [Feature] support multiple protocols in a single ProtocolNode

* add Junjie's code

* Support "Clean" protocol

* Update Grignard_flow_batchreact_single_pumpvalve.json

* test_grignard_add

* add stir device node and example

* Update device_node.py

add print_publish flag to control the node's log output

* NH4Cl_add

* add "HeaterStirrer" device and "HeatChill" action

* add wait time after each pump action for equilibration

* fix stir

* add Separate protocol

* Refactor Separator device and Stir action

* add rotavap_node

* fix stir

* add chiller node

* Move rinsings into PumpTransfer

* Fix SeparateProtocol under refactored Separator device and Stir action

* Supports automatically add new protocol action_types

* fix PumpTransfer protocol because of rinsing

* Add Rotavap and Chiller devices

* fix SeparateProtocol

* add EvaporateProtocol

* add rotavap devices config

* fix HeaterStirrer and SeparatorController IO

* Fix automatically add new protocol action_types

* Add HeaterStirrer and SeparatorController device config

* fix pump protocols

* Fix Evaporate action

* Update evaporate_protocol.py

* add temp_sensor node and add function remap

* update docs

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: q434343 <554662886@qq.com>

* fix aichemeco

* [Bugfix] fix Windows conda packaging

* add file upload api

* update dependencies: force to use 3.11 and remove conflict in WIN64 and OSX64

* update dependencies: force to use 3.11 and remove conflict in WIN64 and OSX64

* Create aichemeco_simple.py

* fix

* update

* add aichemeco file

* MQTT [1/2]: action start (#25)

* add mq

* fix

* clean

* add class

* fix excel

* update bioyong

* add api

* fix

---------

Co-authored-by: caok@dp.tech <xiaoyeqiannian@163.com>

* motor & grasp

* Add Grasp motor support and enhance EleGripper class

- Introduced a new motor configuration for Grasp in sjtu.json.
- Updated EleGripper class to inherit from UniversalDriver and added status property.
- Implemented move_and_rotate method for coordinated movement.
- Adjusted threading logic in EleGripper initialization.
- Registered Grasp motor in ROS2 device node configuration.

This commit enhances the motor control capabilities by integrating the Grasp motor and improving the existing EleGripper functionality.

* fix read data lenth

* Update Grasp.py

* MQTT (2/2): publish Device Status, Action Feedback & Results (#27)

* Add bridges in HostNode for device_status publishing

* Add "bridges" selection (fastapi & mqtt) when app start

* add MQ feedback & result publisher, and fix message converter

* fix UUID converting between ROS and MQ

* lint api model.py

* Continuous controllers: PID, MPC, custom controllers etc. (#23)

* add controller config & wrapper

* add controller setup at app.main

* control loop example

* fix com port

* add agv , ur_arm and raman

* MQTT (3/4): Unified Resources and Sync when starting the server (#28)

* update http upload api

* generate uuid when init device

* example resource json

* fix

* add new example full-content json (device, resource, graph)

* fix full-content json and related reading code

* fix

* add json_schema when initialize resources

* fix

* update schema

* refactor heaterstirrer.dalong

* fix

* fix refactor heaterstirrer.dalong

* refactor syringepump.runze: use ml instead of μL

* Update ilabos/ros/host.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Distributed initialization with self-organizing network (#29)

* add distributed launching option "--without_host"

* fix

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>

* Refactor Workstation: Add resource service and tracking (#30)

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* Create HT_hiwo.json

* add children in resources

* bugfixes

* fix rpc

* add Revvity winprep

---------

Co-authored-by: wjjxxx <43375851+wjjxxx@users.noreply.github.com>
Co-authored-by: 3218923350 <105201755+3218923350@users.noreply.github.com>

* Distributed launch (2/2): distributed resource create (#32)

* add resource_add request to host for slave mode

* add AGV

* fix protocol resources

* optimize host callbacks

* bugfixes

* add revvity registry

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: wjjxxx <43375851+wjjxxx@users.noreply.github.com>

* Refactor Driver Files Structure (#33)

* Integration with pywinauto & recorder
Added execute run and initialize procdure

* 酶标仪状态检测、使用示例,整体流程待测试

* nivo ready version

* Add HPLC driver and example script

- Introduced HPLCDriver class for managing HPLC device status and operations.
- Implemented device status monitoring and command execution via ROS2 actions.
- Added example script (hplc.py) demonstrating how to run commands on the HPLC device.
- Created PlayerUtil and UniversalDriver classes for shared functionality across devices.
- Refactored NivoDriver to utilize the new UniversalDriver structure.
- Enhanced error handling and process management in the NivoDriver.

* 修复start的错误定位

* hplc tested ok

* relative path to build msgs

* template_driver & jiageng devices

* fetch correct status type and action type

* fix mtype fetch

* gpc bus integration

* ilab build

* remove chs

* recipe rename

* modbus update 1

* json available

* hplc & modbus rewrite

* Update AgilentHPLC.py

hplc datafile reader

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* modbus分设备

* gpc

* gpc 2

* fix address

* default register node

* fix MainScreenGPC

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update recipe.yaml

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* feat: add other jiageng PLC device code

* ilabos compile

* correct format

* correct recipe format

* correct setup.py format

* remove unnecessary files

* remove unnecessary files

* Create HT_hiwo.json

* add children in resources

* hplc support sample_id

* correct hplc sample_id

* correct hplc sample_id

* hplc upload

* fix type hint

* oss upload tested ver

* recipe yaml fix for linux

* update installation yaml

* refactor: moved all driver files according to its feat

* merge main to dev

---------

Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>

* add: NMR LH and RU device control (#34)

* Add Registry for device drivers and Support GraphML (#35)

* read chemputer graphml

* read graphml in app/main

* add devices in ros/devices

* add schema for devices

* read registry directory and initialize when entry from main

* Delete devices.py

* Update add_protocol.md

* delete unecessary files

* feat: 2278 devices registry yaml (#36)

* read chemputer graphml

* read graphml in app/main

* add devices in ros/devices

* add schema for devices

* read registry directory and initialize when entry from main

* Delete devices.py

* add: NMR LH and RU device control

* fix: modify jiageng devices registry

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>

* Device/Resource Registry and GraphML support (#37)

* add resource type conversion to PLR

* add resource registry and test

* add docs

* fix registry

* add solenoid_valve_mock, its registry and test

* fix registry for directly using examples

* add EvacuateAndRefillProtocol and testcases

* allow function sequence call in ACTION

* add read & write & extra_info for hardware_interface

* Update device_node.py

* add solenoid valve

* add doc developer guide yaml

* fixes for starting IK station

* add graphml grouping parser

* fix graphml grouping parser

* add communication edge parser

* fix io solenoid valve

* Update .gitignore

* Update plates.yaml

---------

Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>

* Uni-Lab Doc v0.2 (#39)

* add Uni-Lab docs

* change doc name

* Dev (#41)

* Integration with pywinauto & recorder
Added execute run and initialize procdure

* 酶标仪状态检测、使用示例,整体流程待测试

* nivo ready version

* Add HPLC driver and example script

- Introduced HPLCDriver class for managing HPLC device status and operations.
- Implemented device status monitoring and command execution via ROS2 actions.
- Added example script (hplc.py) demonstrating how to run commands on the HPLC device.
- Created PlayerUtil and UniversalDriver classes for shared functionality across devices.
- Refactored NivoDriver to utilize the new UniversalDriver structure.
- Enhanced error handling and process management in the NivoDriver.

* 修复start的错误定位

* hplc tested ok

* relative path to build msgs

* template_driver & jiageng devices

* fetch correct status type and action type

* fix mtype fetch

* gpc bus integration

* ilab build

* remove chs

* recipe rename

* modbus update 1

* json available

* hplc & modbus rewrite

* Update AgilentHPLC.py

hplc datafile reader

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* modbus分设备

* gpc

* gpc 2

* fix address

* default register node

* fix MainScreenGPC

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update recipe.yaml

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* feat: add other jiageng PLC device code

* ilabos compile

* correct format

* correct recipe format

* correct setup.py format

* remove unnecessary files

* remove unnecessary files

* Create HT_hiwo.json

* add children in resources

* hplc support sample_id

* correct hplc sample_id

* correct hplc sample_id

* hplc upload

* fix type hint

* oss upload tested ver

* recipe yaml fix for linux

* update installation yaml

* refactor: moved all driver files according to its feat

* merge main to dev

* add HPLC registry and json

* 升级 ros2-distro-mutex 依赖版本至 0.6

* 修改 ros2-distro-mutex 依赖版本为通配符匹配

* 更新 ros-humble-ilabos-msgs 依赖为 robostack-humble 命名空间

* add resource type conversion to PLR

* add resource registry and test

* feat: 更新oss上传

* fix device id

* add docs

* fix registry

* add solenoid_valve_mock, its registry and test

* fix registry for directly using examples

* add EvacuateAndRefillProtocol and testcases

* allow function sequence call in ACTION

* add read & write & extra_info for hardware_interface

* Update device_node.py

* add solenoid valve

* add doc developer guide yaml

* use robostack-staging

* rclpy version test

* lower rclpy

* ensure 0.6* env

* fixes for starting IK station

* add graphml grouping parser

* fix graphml grouping parser

* add communication edge parser

* fix io solenoid valve

* Update .gitignore

* Update plates.yaml

* Feature/device node later init (#40)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

---------

Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>
Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>

* Dev (#45)

* Integration with pywinauto & recorder
Added execute run and initialize procdure

* 酶标仪状态检测、使用示例,整体流程待测试

* nivo ready version

* Add HPLC driver and example script

- Introduced HPLCDriver class for managing HPLC device status and operations.
- Implemented device status monitoring and command execution via ROS2 actions.
- Added example script (hplc.py) demonstrating how to run commands on the HPLC device.
- Created PlayerUtil and UniversalDriver classes for shared functionality across devices.
- Refactored NivoDriver to utilize the new UniversalDriver structure.
- Enhanced error handling and process management in the NivoDriver.

* 修复start的错误定位

* hplc tested ok

* relative path to build msgs

* template_driver & jiageng devices

* fetch correct status type and action type

* fix mtype fetch

* gpc bus integration

* ilab build

* remove chs

* recipe rename

* modbus update 1

* json available

* hplc & modbus rewrite

* Update AgilentHPLC.py

hplc datafile reader

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* modbus分设备

* gpc

* gpc 2

* fix address

* default register node

* fix MainScreenGPC

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update recipe.yaml

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* feat: add other jiageng PLC device code

* ilabos compile

* correct format

* correct recipe format

* correct setup.py format

* remove unnecessary files

* remove unnecessary files

* Create HT_hiwo.json

* add children in resources

* hplc support sample_id

* correct hplc sample_id

* correct hplc sample_id

* hplc upload

* fix type hint

* oss upload tested ver

* recipe yaml fix for linux

* update installation yaml

* refactor: moved all driver files according to its feat

* merge main to dev

* add HPLC registry and json

* 升级 ros2-distro-mutex 依赖版本至 0.6

* 修改 ros2-distro-mutex 依赖版本为通配符匹配

* 更新 ros-humble-ilabos-msgs 依赖为 robostack-humble 命名空间

* add resource type conversion to PLR

* add resource registry and test

* feat: 更新oss上传

* fix device id

* add docs

* fix registry

* add solenoid_valve_mock, its registry and test

* fix registry for directly using examples

* add EvacuateAndRefillProtocol and testcases

* allow function sequence call in ACTION

* add read & write & extra_info for hardware_interface

* Update device_node.py

* add solenoid valve

* add doc developer guide yaml

* use robostack-staging

* rclpy version test

* lower rclpy

* ensure 0.6* env

* fixes for starting IK station

* add graphml grouping parser

* fix graphml grouping parser

* add communication edge parser

* fix io solenoid valve

* Update .gitignore

* Update plates.yaml

* Feature/device node later init (#40)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* Feature/device node later init (#42)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* action

* plr suc

* plr suc!!

* plr suc!!

* plr suc!!

* plr msgs

* Feature/device node later init (#43)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* action

* plr suc

* plr suc!!

* plr suc!!

* plr suc!!

* plr msgs

* plr

* action

* plr reg fix

* Feature/device node later init (#44)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* action

* plr suc

* plr suc!!

* plr suc!!

* plr suc!!

* plr msgs

* plr

* fix convert error
fix async logic error
added async error print

* new device test

* test resource add

* test resource add

* test resource add

* test resource add

* local env setup

* node type fix
temp fix root_node error
fix convert res from type error

* resource tracker

* fix bug from qhh

* fix bug from qhh

* fix bug from qhh

* fix bug from qhh

* refactor MQTT client logging and connection handling; update group ID in config

* driver_params allow empty

* allow other init param

* fix driver param and enhance type hint

* refactor MQConfig to use double quotes for string literals

* fix wrong function calling

* fix wrong function calling

* fix log for mac

* fix networkx compatibility

* add mqtt loggers

* add action to jsonschema converter

* random client id

* type converter & registry

* correct conversion

* fix action publish only from discovered devices

* add "Bio" tag for action doc generation

* 改进module提示

* Fix doc

* mqtt不连接也可用
性价样例提示

* add docs

* 更新plr test案例

* Update intro.md

* 更新有机案例

* skip

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Junhan Chang <1700011741@pku.edu.cn>

---------

Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>
Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Junhan Chang <1700011741@pku.edu.cn>

* Canonicalize before Open Source (#46)

* big big refactor try01

* refactor 02

---------

Co-authored-by: ck <xiaoyeqiannian@163.com>
Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: q434343 <554662886@qq.com>
Co-authored-by: Xuwznln <xuwznln@gmail.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: wjjxxx <43375851+wjjxxx@users.noreply.github.com>
Co-authored-by: 3218923350 <105201755+3218923350@users.noreply.github.com>
Co-authored-by: Xuwznln <1023025701@qq.com>
Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>
Co-authored-by: Jiawei <91898272+jiawei723@users.noreply.github.com>
Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
2025-04-17 15:09:58 +08:00

471 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import traceback
from datetime import datetime
import os
import re
from typing import TypedDict
import pyautogui
from pywinauto import Application
from pywinauto.application import WindowSpecification
from pywinauto.controls.uiawrapper import UIAWrapper
from pywinauto.uia_element_info import UIAElementInfo
from unilabos.app.oss_upload import oss_upload
from unilabos.device_comms import universal_driver as ud
from unilabos.device_comms.universal_driver import UniversalDriver
class DeviceStatusInfo(TypedDict):
name: str
name_obj: UIAWrapper
status: str
status_obj: UIAWrapper
open_btn: UIAWrapper
close_btn: UIAWrapper
sub_item: UIAWrapper
class DeviceStatus(TypedDict):
VWD: DeviceStatusInfo
JinYangQi: DeviceStatusInfo
Beng: DeviceStatusInfo
ShouJiQi: DeviceStatusInfo
class HPLCDriver(UniversalDriver):
# 设备状态
_device_status: DeviceStatus = None
_is_running: bool = False
_success: bool = False
_finished: int = None
_total_sample_number: int = None
_status_text: str = ""
# 外部传入
_wf_name: str = ""
# 暂时用不到用来支持action name
gantt: str = ""
status: str = ""
@property
def status_text(self) -> str:
return self._status_text
@property
def device_status(self) -> str:
return f", ".join([f"{k}:{v.get('status')}" for k, v in self._device_status.items()])
@property
def could_run(self) -> bool:
return self.driver_init_ok and all([v.get('status') == "空闲" for v in self._device_status.values()])
@property
def driver_init_ok(self) -> bool:
for k, v in self._device_status.items():
if v.get("open_btn") is None:
return False
if v.get("close_btn") is None:
return False
return len(self._device_status) == 4
@property
def is_running(self) -> bool:
return self._is_running
@property
def success(self) -> bool:
return self._success
@property
def finish_status(self) -> str:
return f"{self._finished}/{self._total_sample_number}"
def try_open_sub_device(self, device_name: str = None):
if not self.driver_init_ok:
self._success = False
print(f"仪器还没有初始化完成,无法查询设备:{device_name}")
return
if device_name is None:
for k, v in self._device_status.items():
self.try_open_sub_device(k)
return
target_device_status = self._device_status[device_name]
if target_device_status["status"] == "未就绪":
print(f"尝试打开{device_name}设备")
target_device_status["open_btn"].click()
else:
print(f"{device_name}设备状态不支持打开:{target_device_status['status']}")
def try_close_sub_device(self, device_name: str = None):
if not self.driver_init_ok:
self._success = False
print(f"仪器还没有初始化完成,无法查询设备:{device_name}")
return
if device_name is None:
for k, v in self._device_status.items():
self.try_close_sub_device(k)
return
target_device_status = self._device_status[device_name]
if target_device_status["status"] == "空闲":
print(f"尝试关闭{device_name}设备")
target_device_status["close_btn"].click()
else:
print(f"{device_name}设备状态不支持关闭:{target_device_status['status']}")
def _get_resource_sample_id(self, wf_name, idx):
try:
root = list(self.resource_info[wf_name].values())[0]
# print(root)
plates = root["children"]
plate_01 = list(plates.values())[0]
pots = list(plate_01["children"].values())
return pots[idx]['sample_id']
except Exception as ex:
traceback.print_exc()
def start_sequence(self, wf_name: str, params: str = None, resource: dict = None):
print("!!!!!! 任务启动")
self.resource_info[wf_name] = resource
# 后续workflow_name将同步一下
if self.is_running:
print("设备正在运行,无法启动序列")
self._success = False
return False
if not self.driver_init_ok:
print(f"仪器还没有初始化完成,无法启动序列")
self._success = False
return False
if not self.could_run:
print(f"仪器不处于空闲状态,无法运行")
self._success = False
return False
# 参考:
# with UIPath(u"PREP-LC (联机): 方法和运行控制 ||Window"):
# with UIPath(u"panelNavTabChem||Pane->||Pane->panelControlChemStation||Pane->||Tab->仪器控制||Pane->||Pane->panelChemStation||Pane->PREP-LC (联机): 方法和运行控制 ||Pane->ViewMGR||Pane->MRC view||Pane->||Pane->||Pane->||Pane->||Custom->||Custom"):
# click(u"||Button#[0,1]")
app = Application(backend='uia').connect(title=u"PREP-LC (联机): 方法和运行控制 ")
window = app['PREP-LC (联机): 方法和运行控制']
window.allow_magic_lookup = False
panel_nav_tab = window.child_window(title="panelNavTabChem", auto_id="panelNavTabChem", control_type="Pane")
first_pane = panel_nav_tab.child_window(auto_id="uctlNavTabChem1", control_type="Pane")
panel_control_station = first_pane.child_window(title="panelControlChemStation", auto_id="panelControlChemStation", control_type="Pane")
instrument_control_tab: WindowSpecification = panel_control_station.\
child_window(auto_id="tabControlChem", control_type="Tab").\
child_window(title="仪器控制", auto_id="tabPage1", control_type="Pane").\
child_window(auto_id="uctrlChemStation", control_type="Pane").\
child_window(title="panelChemStation", auto_id="panelChemStation", control_type="Pane").\
child_window(title="PREP-LC (联机): 方法和运行控制 ", control_type="Pane").\
child_window(title="ViewMGR", control_type="Pane").\
child_window(title="MRC view", control_type="Pane").\
child_window(auto_id="mainMrcControlHost", control_type="Pane").\
child_window(control_type="Pane", found_index=0).\
child_window(control_type="Pane", found_index=0).\
child_window(control_type="Custom", found_index=0).\
child_window(control_type="Custom", found_index=0)
instrument_control_tab.dump_tree(3)
btn: UIAWrapper = instrument_control_tab.child_window(auto_id="methodButtonStartSequence", control_type="Button").wrapper_object()
self.start_time = datetime.now()
btn.click()
self._wf_name = wf_name
self._success = True
return True
def check_status(self):
app = Application(backend='uia').connect(title=u"PREP-LC (联机): 方法和运行控制 ")
window = app['PREP-LC (联机): 方法和运行控制']
ui_window = window.child_window(title="靠顶部", control_type="Group").\
child_window(title="状态", control_type="ToolBar").\
child_window(title="项目", control_type="Button", found_index=0)
# 检测pixel的颜色
element_info: UIAElementInfo = ui_window.element_info
rectangle = element_info.rectangle
point_x = int(rectangle.left + rectangle.width() * 0.15)
point_y = int(rectangle.top + rectangle.height() * 0.15)
r, g, b = pyautogui.pixel(point_x, point_y)
if 270 > r > 250 and 200 > g > 180 and b < 10: # 是黄色
self._is_running = False
self._status_text = "Not Ready"
elif r > 110 and g > 190 and 50 < b < 60:
self._is_running = False
self._status_text = "Ready"
elif 75 > r > 65 and 135 > g > 120 and 240 > b > 230:
self._is_running = True
self._status_text = "Running"
else:
print(point_x, point_y, "未知的状态", r, g, b)
def extract_data_from_txt(self, file_path):
# 打开文件
print(file_path)
with open(file_path, mode='r', encoding='utf-16') as f:
lines = f.readlines()
# 定义一个标志变量来判断是否已经找到“馏分列表”
started = False
data = []
for line in lines:
# 查找“馏分列表”,并开始提取后续行
if line.startswith("-----|-----|-----"):
started = True
continue # 跳过当前行
if started:
# 遇到"==="表示结束读取
if '=' * 80 in line:
break
# 使用正则表达式提取馏分、孔、位置和原因
res = re.split(r'\s+', line.strip())
if res:
fraction, hole, position, reason = res[0], res[1], res[2], res[-1]
data.append({
'馏分': fraction,
'': hole,
'位置': position,
'原因': reason.strip()
})
return data
def get_data_file(self, mat_index: str = None, after_time: datetime = None) -> tuple[str, str]:
"""
获取数据文件
after_time: 由于HPLC是启动后生成一个带时间的目录所以会选取after_time后的文件
"""
if mat_index is None:
print(f"mat_index不能为空")
return None
if after_time is None:
after_time = self.start_time
files = [i for i in os.listdir(self.data_file_path) if i.startswith(self.using_method)]
time_to_files: list[tuple[datetime, str]] = [(datetime.strptime(i.split(" ", 1)[1], "%Y-%m-%d %H-%M-%S"), i) for i in files]
time_to_files.sort(key=lambda x: x[0])
choose_folder = None
for i in time_to_files:
if i[0] > after_time:
print(i[0], after_time)
print(f"选取时间{datetime.strftime(after_time, '%Y-%m-%d %H-%M-%S')}之后的文件夹{i[1]}")
choose_folder = i[1]
break
if choose_folder is None:
print(f"没有找到{self.using_method} {datetime.strftime(after_time, '%Y-%m-%d %H-%M-%S')}之后的文件夹")
return None
current_data_path = os.path.join(self.data_file_path, choose_folder)
# 需要匹配 数字数字数字-.* 001-P2-E1-DQJ-4-70.D
target_row = [i for i in os.listdir(current_data_path) if re.match(r"\d{3}-.*", i)]
index2filepath = {int(k.split("-")[0]): os.path.join(current_data_path, k) for k in target_row}
print(f"查找文件{mat_index}")
if int(mat_index) not in index2filepath:
print(f"没有找到{mat_index}的文件 已找到:{index2filepath}")
return None
mat_final_path = index2filepath[int(mat_index)]
pdf = os.path.join(mat_final_path, "Report.PDF")
txt = os.path.join(mat_final_path, "Report.TXT")
fractions = self.extract_data_from_txt(txt)
print(fractions)
return pdf, txt
def __init__(self, driver_debug=False):
super().__init__()
self.data_file_path = r"D:\ChemStation\1\Data"
self.using_method = f"1106-dqj-4-64"
self.start_time = datetime.now()
self._device_status = dict()
self.resource_info: dict[str, dict] = dict()
# 启动所有监控器
self.checkers = [
InstrumentChecker(self, 1),
RunningChecker(self, 1),
RunningResultChecker(self, 1),
]
if not driver_debug:
for checker in self.checkers:
checker.start_monitoring()
class DriverChecker(ud.DriverChecker):
driver: HPLCDriver
class InstrumentChecker(DriverChecker):
_instrument_control_tab = None
_instrument_control_tab_wrapper = None
def get_instrument_status(self):
if self._instrument_control_tab is not None:
return self._instrument_control_tab
# 连接到目标窗口
app = Application(backend='uia').connect(title=u"PREP-LC (联机): 方法和运行控制 ")
window = app['PREP-LC (联机): 方法和运行控制']
window.allow_magic_lookup = False
panel_nav_tab = window.child_window(title="panelNavTabChem", auto_id="panelNavTabChem", control_type="Pane")
first_pane = panel_nav_tab.child_window(auto_id="uctlNavTabChem1", control_type="Pane")
panel_control_station = first_pane.child_window(title="panelControlChemStation", auto_id="panelControlChemStation", control_type="Pane")
instrument_control_tab: WindowSpecification = panel_control_station.\
child_window(auto_id="tabControlChem", control_type="Tab").\
child_window(title="仪器控制", auto_id="tabPage1", control_type="Pane").\
child_window(auto_id="uctrlChemStation", control_type="Pane").\
child_window(title="panelChemStation", auto_id="panelChemStation", control_type="Pane").\
child_window(title="PREP-LC (联机): 方法和运行控制 ", control_type="Pane").\
child_window(title="ViewMGR", control_type="Pane").\
child_window(title="MRC view", control_type="Pane").\
child_window(auto_id="mainMrcControlHost", control_type="Pane").\
child_window(control_type="Pane", found_index=0).\
child_window(control_type="Pane", found_index=0).\
child_window(control_type="Custom", found_index=0).\
child_window(best_match="Custom6").\
child_window(auto_id="ListBox_DashboardPanel", control_type="List")
if self._instrument_control_tab is None:
self._instrument_control_tab = instrument_control_tab
self._instrument_control_tab_wrapper = instrument_control_tab.wrapper_object()
return self._instrument_control_tab
def check(self):
self.get_instrument_status()
if self._instrument_control_tab_wrapper is None or self._instrument_control_tab is None:
return
item: UIAWrapper
index = 0
keys = list(self.driver._device_status.keys())
for item in self._instrument_control_tab_wrapper.children():
info: UIAElementInfo = item.element_info
if info.control_type == "ListItem" and item.window_text() == "Agilent.RapidControl.StatusDashboard.PluginViewModel":
sub_item: WindowSpecification = self._instrument_control_tab.\
child_window(title="Agilent.RapidControl.StatusDashboard.PluginViewModel", control_type="ListItem", found_index=index).\
child_window(control_type="Custom", found_index=0)
if index < len(keys):
deviceStatusInfo = self.driver._device_status[keys[index]]
name = deviceStatusInfo["name"]
deviceStatusInfo["status"] = deviceStatusInfo["status_obj"].window_text()
print(name, index, deviceStatusInfo["status"], "刷新")
if deviceStatusInfo["open_btn"] is not None and deviceStatusInfo["close_btn"] is not None:
index += 1
continue
else:
name_obj = sub_item.child_window(control_type="Text", found_index=0).wrapper_object()
name = name_obj.window_text()
self.driver._device_status[name] = dict()
self.driver._device_status[name]["name_obj"] = name_obj
self.driver._device_status[name]["name"] = name
print(name, index)
status = sub_item.child_window(control_type="Custom", found_index=0).\
child_window(auto_id="TextBlock_StateLabel", control_type="Text")
status_obj: UIAWrapper = status.wrapper_object()
self.driver._device_status[name]["status_obj"] = status_obj
self.driver._device_status[name]["status"] = status_obj.window_text()
print(status.window_text())
sub_item = sub_item.wrapper_object()
found_index = 0
open_btn = None
close_btn = None
for btn in sub_item.children():
if btn.element_info.control_type == "Button":
found_index += 1
if found_index == 5:
open_btn = btn
elif found_index == 6:
close_btn = btn
self.driver._device_status[name]["open_btn"] = open_btn
self.driver._device_status[name]["close_btn"] = close_btn
index += 1
class RunningChecker(DriverChecker):
def check(self):
self.driver.check_status()
class RunningResultChecker(DriverChecker):
_finished: UIAWrapper = None
_total_sample_number: UIAWrapper = None
def check(self):
if self._finished is None or self._total_sample_number is None:
app = Application(backend='uia').connect(title=u"PREP-LC (联机): 方法和运行控制 ")
window = app['PREP-LC (联机): 方法和运行控制']
window.allow_magic_lookup = False
panel_nav_tab = window.child_window(title="panelNavTabChem", auto_id="panelNavTabChem", control_type="Pane")
first_pane = panel_nav_tab.child_window(auto_id="uctlNavTabChem1", control_type="Pane")
panel_control_station = first_pane.child_window(title="panelControlChemStation", auto_id="panelControlChemStation", control_type="Pane")
instrument_control_tab: WindowSpecification = panel_control_station.\
child_window(auto_id="tabControlChem", control_type="Tab").\
child_window(title="仪器控制", auto_id="tabPage1", control_type="Pane").\
child_window(auto_id="uctrlChemStation", control_type="Pane").\
child_window(title="panelChemStation", auto_id="panelChemStation", control_type="Pane").\
child_window(title="PREP-LC (联机): 方法和运行控制 ", control_type="Pane").\
child_window(title="ViewMGR", control_type="Pane").\
child_window(title="MRC view", control_type="Pane").\
child_window(auto_id="mainMrcControlHost", control_type="Pane").\
child_window(control_type="Pane", found_index=0).\
child_window(control_type="Pane", found_index=0).\
child_window(control_type="Custom", found_index=0).\
child_window(auto_id="mainControlExpanderSampleInformation", control_type="Group").\
child_window(auto_id="controlsSampleInfo", control_type="Custom")
self._finished = instrument_control_tab.child_window(best_match="Static15").wrapper_object()
self._total_sample_number = instrument_control_tab.child_window(best_match="Static16").wrapper_object()
try:
temp = int(self._finished.window_text())
if self.driver._finished is None or temp > self.driver._finished:
if self.driver._finished is None:
self.driver._finished = 0
for i in range(self.driver._finished, temp):
sample_id = self.driver._get_resource_sample_id(self.driver._wf_name, i) # 从0开始计数
pdf, txt = self.driver.get_data_file(i + 1)
device_id = self.driver.device_id if hasattr(self.driver, "device_id") else "default"
oss_upload(pdf, f"hplc/{sample_id}/{os.path.basename(pdf)}", process_key="example", device_id=device_id)
oss_upload(txt, f"hplc/{sample_id}/{os.path.basename(txt)}", process_key="HPLC-txt-result", device_id=device_id)
# self.driver.extract_data_from_txt()
except Exception as ex:
self.driver._finished = 0
print("转换数字出错", ex)
try:
self.driver._total_sample_number = int(self._total_sample_number.window_text())
except Exception as ex:
self.driver._total_sample_number = 0
print("转换数字出错", ex)
# 示例用法
if __name__ == "__main__":
# obj = HPLCDriver.__new__(HPLCDriver)
# obj.start_sequence()
# obj = HPLCDriver.__new__(HPLCDriver)
# obj.data_file_path = r"D:\ChemStation\1\Data"
# obj.using_method = r"1106-dqj-4-64"
# obj.get_data_file("001", after_time=datetime(2024, 11, 6, 19, 3, 6))
obj = HPLCDriver.__new__(HPLCDriver)
obj.data_file_path = r"D:\ChemStation\1\Data"
obj.using_method = r"1106-dqj-4-64"
obj._wf_name = "test"
obj.resource_info = {
"test": {
"1": {
"children": {
"11": {
"children": {
"111": {
"sample_id": "sample-1"
},
"112": {
"sample_id": "sample-2"
}
}
}
}
}
}
}
sample_id = obj._get_resource_sample_id("test", 0)
pdf, txt = obj.get_data_file("1", after_time=datetime(2024, 11, 6, 19, 3, 6))
oss_upload(pdf, f"hplc/{sample_id}/{os.path.basename(pdf)}", process_key="example")
oss_upload(txt, f"hplc/{sample_id}/{os.path.basename(txt)}", process_key="HPLC-txt-result")
# driver = HPLCDriver()
# for i in range(10000):
# print({k: v for k, v in driver._device_status.items() if isinstance(v, str)})
# print(driver.device_status)
# print(driver.could_run)
# print(driver.driver_init_ok)
# print(driver.is_running)
# print(driver.finish_status)
# print(driver.status_text)
# time.sleep(5)