mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 04:51:10 +00:00
Initial commit
This commit is contained in:
577
docs/developer_guide/action_includes.md
Normal file
577
docs/developer_guide/action_includes.md
Normal file
@@ -0,0 +1,577 @@
|
||||
## 简单单变量动作函数
|
||||
|
||||
|
||||
### `SendCmd`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/SendCmd.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
## 常量有机化学操作
|
||||
|
||||
Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab.io/chemputer/xdl/standard/full_steps_specification.html#),包含有机合成实验中常见的操作,如加热、搅拌、冷却等。
|
||||
|
||||
|
||||
|
||||
### `Clean`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Clean.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `EvacuateAndRefill`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/EvacuateAndRefill.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `Evaporate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Evaporate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `HeatChill`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/HeatChill.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `HeatChillStart`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/HeatChillStart.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `HeatChillStop`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/HeatChillStop.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `PumpTransfer`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/PumpTransfer.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `Separate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Separate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `Stir`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Stir.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
## 移液工作站及相关生物自动化设备操作
|
||||
|
||||
Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.org/user_guide/index.html),包含生物实验中常见的操作,如移液、混匀、离心等。
|
||||
|
||||
|
||||
|
||||
### `LiquidHandlerAspirate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerAspirate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerDiscardTips`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerDiscardTips.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerDispense`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerDispense.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerDropTips`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerDropTips.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerDropTips96`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerDropTips96.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerMoveLid`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMoveLid.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerMovePlate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMovePlate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerMoveResource`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMoveResource.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerPickUpTips`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerPickUpTips.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerPickUpTips96`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerPickUpTips96.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerReturnTips`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerReturnTips.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerReturnTips96`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerReturnTips96.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerStamp`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerStamp.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `LiquidHandlerTransfer`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerTransfer.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
## 多工作站及小车运行、物料转移
|
||||
|
||||
|
||||
### `AGVTransfer`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/AGVTransfer.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### `WorkStationRun`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/WorkStationRun.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
## 机械臂、夹爪等机器人设备
|
||||
|
||||
Uni-Lab 机械臂、机器人、夹爪和导航指令集沿用 ROS2 的 `control_msgs` 和 `nav2_msgs`:
|
||||
|
||||
|
||||
### `FollowJointTrajectory`
|
||||
|
||||
```yaml
|
||||
# The trajectory for all revolute, continuous or prismatic joints
|
||||
trajectory_msgs/JointTrajectory trajectory
|
||||
# The trajectory for all planar or floating joints (i.e. individual joints with more than one DOF)
|
||||
trajectory_msgs/MultiDOFJointTrajectory multi_dof_trajectory
|
||||
|
||||
# Tolerances for the trajectory. If the measured joint values fall
|
||||
# outside the tolerances the trajectory goal is aborted. Any
|
||||
# tolerances that are not specified (by being omitted or set to 0) are
|
||||
# set to the defaults for the action server (often taken from the
|
||||
# parameter server).
|
||||
|
||||
# Tolerances applied to the joints as the trajectory is executed. If
|
||||
# violated, the goal aborts with error_code set to
|
||||
# PATH_TOLERANCE_VIOLATED.
|
||||
JointTolerance[] path_tolerance
|
||||
JointComponentTolerance[] component_path_tolerance
|
||||
|
||||
# To report success, the joints must be within goal_tolerance of the
|
||||
# final trajectory value. The goal must be achieved by time the
|
||||
# trajectory ends plus goal_time_tolerance. (goal_time_tolerance
|
||||
# allows some leeway in time, so that the trajectory goal can still
|
||||
# succeed even if the joints reach the goal some time after the
|
||||
# precise end time of the trajectory).
|
||||
#
|
||||
# If the joints are not within goal_tolerance after "trajectory finish
|
||||
# time" + goal_time_tolerance, the goal aborts with error_code set to
|
||||
# GOAL_TOLERANCE_VIOLATED
|
||||
JointTolerance[] goal_tolerance
|
||||
JointComponentTolerance[] component_goal_tolerance
|
||||
builtin_interfaces/Duration goal_time_tolerance
|
||||
|
||||
---
|
||||
int32 error_code
|
||||
int32 SUCCESSFUL = 0
|
||||
int32 INVALID_GOAL = -1
|
||||
int32 INVALID_JOINTS = -2
|
||||
int32 OLD_HEADER_TIMESTAMP = -3
|
||||
int32 PATH_TOLERANCE_VIOLATED = -4
|
||||
int32 GOAL_TOLERANCE_VIOLATED = -5
|
||||
|
||||
# Human readable description of the error code. Contains complementary
|
||||
# information that is especially useful when execution fails, for instance:
|
||||
# - INVALID_GOAL: The reason for the invalid goal (e.g., the requested
|
||||
# trajectory is in the past).
|
||||
# - INVALID_JOINTS: The mismatch between the expected controller joints
|
||||
# and those provided in the goal.
|
||||
# - PATH_TOLERANCE_VIOLATED and GOAL_TOLERANCE_VIOLATED: Which joint
|
||||
# violated which tolerance, and by how much.
|
||||
string error_string
|
||||
|
||||
---
|
||||
std_msgs/Header header
|
||||
string[] joint_names
|
||||
trajectory_msgs/JointTrajectoryPoint desired
|
||||
trajectory_msgs/JointTrajectoryPoint actual
|
||||
trajectory_msgs/JointTrajectoryPoint error
|
||||
|
||||
string[] multi_dof_joint_names
|
||||
trajectory_msgs/MultiDOFJointTrajectoryPoint multi_dof_desired
|
||||
trajectory_msgs/MultiDOFJointTrajectoryPoint multi_dof_actual
|
||||
trajectory_msgs/MultiDOFJointTrajectoryPoint multi_dof_error
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `GripperCommand`
|
||||
|
||||
```yaml
|
||||
GripperCommand command
|
||||
---
|
||||
float64 position # The current gripper gap size (in meters)
|
||||
float64 effort # The current effort exerted (in Newtons)
|
||||
bool stalled # True iff the gripper is exerting max effort and not moving
|
||||
bool reached_goal # True iff the gripper position has reached the commanded setpoint
|
||||
---
|
||||
float64 position # The current gripper gap size (in meters)
|
||||
float64 effort # The current effort exerted (in Newtons)
|
||||
bool stalled # True iff the gripper is exerting max effort and not moving
|
||||
bool reached_goal # True iff the gripper position has reached the commanded setpoint
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `JointTrajectory`
|
||||
|
||||
```yaml
|
||||
trajectory_msgs/JointTrajectory trajectory
|
||||
---
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `PointHead`
|
||||
|
||||
```yaml
|
||||
geometry_msgs/PointStamped target
|
||||
geometry_msgs/Vector3 pointing_axis
|
||||
string pointing_frame
|
||||
builtin_interfaces/Duration min_duration
|
||||
float64 max_velocity
|
||||
---
|
||||
---
|
||||
float64 pointing_angle_error
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `SingleJointPosition`
|
||||
|
||||
```yaml
|
||||
float64 position
|
||||
builtin_interfaces/Duration min_duration
|
||||
float64 max_velocity
|
||||
---
|
||||
---
|
||||
std_msgs/Header header
|
||||
float64 position
|
||||
float64 velocity
|
||||
float64 error
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `AssistedTeleop`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
builtin_interfaces/Duration time_allowance
|
||||
---
|
||||
#result definition
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback
|
||||
builtin_interfaces/Duration current_teleop_duration
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `BackUp`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/Point target
|
||||
float32 speed
|
||||
builtin_interfaces/Duration time_allowance
|
||||
---
|
||||
#result definition
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
float32 distance_traveled
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `ComputePathThroughPoses`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/PoseStamped[] goals
|
||||
geometry_msgs/PoseStamped start
|
||||
string planner_id
|
||||
bool use_start # If false, use current robot pose as path start, if true, use start above instead
|
||||
---
|
||||
#result definition
|
||||
nav_msgs/Path path
|
||||
builtin_interfaces/Duration planning_time
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `ComputePathToPose`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/PoseStamped goal
|
||||
geometry_msgs/PoseStamped start
|
||||
string planner_id
|
||||
bool use_start # If false, use current robot pose as path start, if true, use start above instead
|
||||
---
|
||||
#result definition
|
||||
nav_msgs/Path path
|
||||
builtin_interfaces/Duration planning_time
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `DriveOnHeading`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/Point target
|
||||
float32 speed
|
||||
builtin_interfaces/Duration time_allowance
|
||||
---
|
||||
#result definition
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
float32 distance_traveled
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `DummyBehavior`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
std_msgs/String command
|
||||
---
|
||||
#result definition
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `FollowPath`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
nav_msgs/Path path
|
||||
string controller_id
|
||||
string goal_checker_id
|
||||
---
|
||||
#result definition
|
||||
std_msgs/Empty result
|
||||
---
|
||||
#feedback definition
|
||||
float32 distance_to_goal
|
||||
float32 speed
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `FollowWaypoints`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/PoseStamped[] poses
|
||||
---
|
||||
#result definition
|
||||
int32[] missed_waypoints
|
||||
---
|
||||
#feedback definition
|
||||
uint32 current_waypoint
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `NavigateThroughPoses`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/PoseStamped[] poses
|
||||
string behavior_tree
|
||||
---
|
||||
#result definition
|
||||
std_msgs/Empty result
|
||||
---
|
||||
#feedback definition
|
||||
geometry_msgs/PoseStamped current_pose
|
||||
builtin_interfaces/Duration navigation_time
|
||||
builtin_interfaces/Duration estimated_time_remaining
|
||||
int16 number_of_recoveries
|
||||
float32 distance_remaining
|
||||
int16 number_of_poses_remaining
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `NavigateToPose`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
geometry_msgs/PoseStamped pose
|
||||
string behavior_tree
|
||||
---
|
||||
#result definition
|
||||
std_msgs/Empty result
|
||||
---
|
||||
#feedback definition
|
||||
geometry_msgs/PoseStamped current_pose
|
||||
builtin_interfaces/Duration navigation_time
|
||||
builtin_interfaces/Duration estimated_time_remaining
|
||||
int16 number_of_recoveries
|
||||
float32 distance_remaining
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `SmoothPath`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
nav_msgs/Path path
|
||||
string smoother_id
|
||||
builtin_interfaces/Duration max_smoothing_duration
|
||||
bool check_for_collisions
|
||||
---
|
||||
#result definition
|
||||
nav_msgs/Path path
|
||||
builtin_interfaces/Duration smoothing_duration
|
||||
bool was_completed
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `Spin`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
float32 target_yaw
|
||||
builtin_interfaces/Duration time_allowance
|
||||
---
|
||||
#result definition
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
float32 angular_distance_traveled
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `Wait`
|
||||
|
||||
```yaml
|
||||
#goal definition
|
||||
builtin_interfaces/Duration time
|
||||
---
|
||||
#result definition
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
builtin_interfaces/Duration time_left
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
7
docs/developer_guide/actions.md
Normal file
7
docs/developer_guide/actions.md
Normal file
@@ -0,0 +1,7 @@
|
||||
(actions)=
|
||||
# Uni-Lab 动作指令集
|
||||
|
||||
Uni-Lab 当前动作指令集设计时,主要考虑兼容领域历史开源工具。目前包括以下场景:
|
||||
|
||||
```{include} action_includes.md
|
||||
```
|
||||
37
docs/developer_guide/add_action.md
Normal file
37
docs/developer_guide/add_action.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 添加新动作指令(Action)
|
||||
|
||||
1. 在 `unilabos_msgs/action` 中新建实验操作名和参数列表,如 `MyDeviceCmd.action`。一个 Action 定义由三个部分组成,分别是目标(Goal)、结果(Result)和反馈(Feedback),之间使用 `---` 分隔:
|
||||
|
||||
```action
|
||||
# 目标(Goal)
|
||||
string command
|
||||
---
|
||||
# 结果(Result)
|
||||
bool success
|
||||
---
|
||||
# 反馈(Feedback)
|
||||
```
|
||||
|
||||
2. 在 `unilabos_msgs/CMakeLists.txt` 中添加新定义的 action
|
||||
|
||||
```cmake
|
||||
add_action_files(
|
||||
FILES
|
||||
MyDeviceCmd.action
|
||||
)
|
||||
```
|
||||
|
||||
3. 因为在指令集中新建了指令,因此调试时需要编译,并在终端环境中加载临时路径:
|
||||
|
||||
```bash
|
||||
cd unilabos_msgs
|
||||
colcon build
|
||||
source ./install/local_setup.sh
|
||||
cd ..
|
||||
```
|
||||
|
||||
调试成功后,发起 pull request,Uni-Lab 的 CI/CD 系统会自动将新的指令集编译打包,mamba执行升级即可永久生效:
|
||||
|
||||
```bash
|
||||
mamba update ros-humble-unilabos-msgs -c http://quetz.dp.tech:8088/get/unilab -c robostack-humble -c robostack-staging
|
||||
```
|
||||
200
docs/developer_guide/add_device.md
Normal file
200
docs/developer_guide/add_device.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# 添加新设备
|
||||
|
||||
在 Uni-Lab 中,设备(Device)是实验操作的基础单元。Uni-Lab 使用**注册表机制**来兼容管理种类繁多的设备驱动程序。回顾 {ref}`instructions` 中的概念,抽象的设备对外拥有【话题】【服务】【动作】三种通信机制,因此将设备添加进 Uni-Lab,实际上是将设备驱动中的三种机制映射到 Uni-Lab 标准指令集上。
|
||||
|
||||
能被 Uni-Lab 添加的驱动程序类型有以下种类:
|
||||
|
||||
1. Python Class,如
|
||||
|
||||
```python
|
||||
class MockGripper:
|
||||
def __init__(self):
|
||||
self._position: float = 0.0
|
||||
self._velocity: float = 2.0
|
||||
self._torque: float = 0.0
|
||||
self._status = "Idle"
|
||||
|
||||
@property
|
||||
def position(self) -> float:
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def velocity(self) -> float:
|
||||
return self._velocity
|
||||
|
||||
@property
|
||||
def torque(self) -> float:
|
||||
return self._torque
|
||||
|
||||
# 会被自动识别的设备属性,接入 Uni-Lab 时会定时对外广播
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self._status
|
||||
|
||||
# 会被自动识别的设备动作,接入 Uni-Lab 时会作为 ActionServer 接受任意控制者的指令
|
||||
@status.setter
|
||||
def status(self, target):
|
||||
self._status = target
|
||||
|
||||
# 需要在注册表添加的设备动作,接入 Uni-Lab 时会作为 ActionServer 接受任意控制者的指令
|
||||
def push_to(self, position: float, torque: float, velocity: float = 0.0):
|
||||
self._status = "Running"
|
||||
current_pos = self.position
|
||||
if velocity == 0.0:
|
||||
velocity = self.velocity
|
||||
|
||||
move_time = abs(position - current_pos) / velocity
|
||||
for i in range(20):
|
||||
self._position = current_pos + (position - current_pos) / 20 * (i+1)
|
||||
self._torque = torque / (20 - i)
|
||||
self._velocity = velocity
|
||||
time.sleep(move_time / 20)
|
||||
self._torque = torque
|
||||
self._status = "Idle"
|
||||
```
|
||||
|
||||
Python 类设备驱动在完成注册表后可以直接在 Uni-Lab 使用。
|
||||
|
||||
2. C# Class,如
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class MockGripper
|
||||
{
|
||||
// 会被自动识别的设备属性,接入 Uni-Lab 时会定时对外广播
|
||||
public double position { get; private set; } = 0.0;
|
||||
public double velocity { get; private set; } = 2.0;
|
||||
public double torque { get; private set; } = 0.0;
|
||||
public string status { get; private set; } = "Idle";
|
||||
|
||||
// 需要在注册表添加的设备动作,接入 Uni-Lab 时会作为 ActionServer 接受任意控制者的指令
|
||||
public async Task PushToAsync(double Position, double Torque, double Velocity = 0.0)
|
||||
{
|
||||
status = "Running";
|
||||
double currentPos = Position;
|
||||
if (Velocity == 0.0)
|
||||
{
|
||||
velocity = Velocity;
|
||||
}
|
||||
double moveTime = Math.Abs(Position - currentPos) / velocity;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
position = currentPos + (Position - currentPos) / 20 * (i + 1);
|
||||
torque = Torque / (20 - i);
|
||||
velocity = Velocity;
|
||||
await Task.Delay((int)(moveTime * 1000 / 20)); // Convert seconds to milliseconds
|
||||
}
|
||||
torque = Torque;
|
||||
status = "Idle";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
C# 驱动设备在完成注册表后,需要调用 Uni-Lab C# 编译后才能使用,但只需一次。
|
||||
|
||||
## 注册表文件位置
|
||||
|
||||
Uni-Lab 启动时会自动读取默认注册表路径 `unilabos/registry/devices` 下的所有注册设备。您也可以任意维护自己的注册表路径,只需要在 Uni-Lab 启动时使用 `--registry` 参数将路径添加即可。
|
||||
|
||||
在 `<path-to-registry>/devices` 中新建一个 yaml 文件,即可开始撰写。您可以将多个设备写到同一个 yaml 文件中。
|
||||
|
||||
## 注册表的结构
|
||||
|
||||
1. 顶层名称:每个设备的注册表以设备名称开头,例如 `new_device`, `gripper.mock`。
|
||||
1. `class` 字段:定义设备的模块路径和驱动程序语言。
|
||||
1. `status_types` 字段:定义设备定时对 Uni-Lab 实验室内发送的属性名及其类型。
|
||||
1. `action_value_mappings` 字段:定义设备支持的动作及其目标、反馈和结果。
|
||||
1. `schema` 字段:定义设备定时对 Uni-Lab 云端监控发送的属性名及其类型、描述(非必须)
|
||||
|
||||
## 创建新的注册表教程
|
||||
|
||||
1. 创建文件
|
||||
在 devices 文件夹中创建一个新的 YAML 文件,例如 `new_device.yaml`。
|
||||
2. 定义设备名称
|
||||
在文件中定义设备的顶层名称,例如:`new_device` 或 `gripper.mock`
|
||||
3. 定义设备的类信息
|
||||
添加设备的模块路径和类型:
|
||||
|
||||
```yaml
|
||||
gripper.mock:
|
||||
class: # 定义设备的类信息
|
||||
module: unilabos.devices.gripper.mock:MockGripper
|
||||
type: python # 指定驱动语言为 Python
|
||||
status_types:
|
||||
position: Float64
|
||||
torque: Float64
|
||||
status: String
|
||||
```
|
||||
|
||||
4. 定义设备的定时发布属性。注意,对于 Python Class 来说,PROP 是 class 的 `property`,或满足能被 `getattr(cls, PROP)` 或 `cls.get_PROP` 读取到的属性值的对象。
|
||||
|
||||
```yaml
|
||||
status_types:
|
||||
PROP: TYPE
|
||||
```
|
||||
5. 定义设备支持的动作
|
||||
添加设备支持的动作及其目标、反馈和结果:
|
||||
|
||||
```yaml
|
||||
action_value_mappings:
|
||||
set_speed:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: speed
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
```
|
||||
|
||||
在 devices 文件夹中的 YAML 文件中,action_value_mappings 是用来将驱动内的动作函数,映射到 Uni-Lab 标准动作(actions)及其目标参数值(goal)、反馈值(feedback)和结果值(result)的映射规则。若在 Uni-Lab 指令集内找不到符合心意的,请【创建新指令】。
|
||||
|
||||
```yaml
|
||||
action_value_mappings:
|
||||
<action_name>: # <action_name>:动作的名称
|
||||
# start:启动设备或某个功能。
|
||||
# stop:停止设备或某个功能。
|
||||
# set_speed:设置设备的速度。
|
||||
# set_temperature:设置设备的温度。
|
||||
# move_to_position:移动设备到指定位置。
|
||||
# stir:执行搅拌操作。
|
||||
# heatchill:执行加热或冷却操作。
|
||||
# send_nav_task:发送导航任务(例如机器人导航)。
|
||||
# set_timer:设置设备的计时器。
|
||||
# valve_open_cmd:打开阀门。
|
||||
# valve_close_cmd:关闭阀门。
|
||||
# execute_command_from_outer:执行外部命令。
|
||||
# push_to:控制设备推送到某个位置(例如机械爪)。
|
||||
# move_through_points:导航设备通过多个点。
|
||||
|
||||
type: <ActionType> # 动作的类型,表示动作的功能
|
||||
# 根据动作的功能选择合适的类型,请查阅 Uni-Lab 已支持的指令集。
|
||||
|
||||
goal: # 定义动作的目标值映射,表示需要传递给设备的参数。
|
||||
<goal_key>: <mapped_value> #确定设备需要的输入参数,并将其映射到设备的字段。
|
||||
|
||||
feedback: # 定义动作的反馈值映射,表示设备执行动作时返回的实时状态。
|
||||
<feedback_key>: <mapped_value>
|
||||
result: # 定义动作的结果值映射,表示动作完成后返回的最终结果。
|
||||
<result_key>: <mapped_value>
|
||||
```
|
||||
|
||||
6. 定义设备的网页展示属性类型,这部分会被用于在 Uni-Lab 网页端渲染成状态监控
|
||||
添加设备的属性模式,包括属性类型和描述:
|
||||
|
||||
```yaml
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
description: The status of the device
|
||||
speed:
|
||||
type: number
|
||||
description: The speed of the device
|
||||
required:
|
||||
- status
|
||||
- speed
|
||||
additionalProperties: false
|
||||
```
|
||||
50
docs/developer_guide/add_protocol.md
Normal file
50
docs/developer_guide/add_protocol.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 添加新实验操作(Protocol)
|
||||
|
||||
在 `Uni-Lab` 中,实验操作(Protocol)指的是**对实验有意义的单个完整动作**——加入某种液体多少量;萃取分液;洗涤仪器;机械+末端执行器等等,就像实验步骤文字书写的那样。
|
||||
|
||||
而这些对实验有意义的单个完整动作,**一般需要多个设备的协同**,或者同一设备连续动作,还依赖于他们的**物理连接关系(管道相连;机械臂可转运)**。`Protocol` 根据实验操作目标和设备物理连接关系,通过 `unilabos/compile` 中的“编译”过程产生硬件可执行的机器指令,并依次执行。
|
||||
|
||||
开发一个 `Protocol` 一般共需要修改6个文件:
|
||||
|
||||
1. 在 `unilabos_msgs/action` 中新建实验操作名和参数列表,如 `PumpTransfer.action`。一个 Action 定义由三个部分组成,分别是目标(Goal)、结果(Result)和反馈(Feedback),之间使用 `---` 分隔:
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/PumpTransfer.action
|
||||
```
|
||||
|
||||
2. 在 `unilabos_msgs/CMakeLists.txt` 中添加新定义的 action
|
||||
因为在指令集中新建了指令,因此调试时需要编译,并在终端环境中加载临时路径:
|
||||
```bash
|
||||
cd unilabos_msgs
|
||||
colcon build
|
||||
source ./install/local_setup.sh
|
||||
cd ..
|
||||
```
|
||||
|
||||
调试成功后,发起 pull request,Uni-Lab 的 CI/CD 系统会自动将新的指令集编译打包,mamba执行升级即可永久生效:
|
||||
|
||||
```bash
|
||||
mamba update ros-humble-unilabos-msgs -c http://quetz.dp.tech:8088/get/unilab -c robostack-humble -c robostack-staging
|
||||
```
|
||||
|
||||
3. 在 `unilabos/messages/__init__.py` 中添加 Pydantic 定义的实验操作名和参数列表
|
||||
```{literalinclude} ../../unilabos/messages/__init__.py
|
||||
:start-after: Start Protocols
|
||||
:end-before: End Protocols
|
||||
```
|
||||
|
||||
4. 在 `unilabos/compile` 中新建编译为机器指令的函数,函数入参为设备连接图 `G` 和实验操作参数。
|
||||
```{literalinclude} ../../unilabos/compile/pump_protocol.py
|
||||
:start-after: Pump protocol compilation
|
||||
:end-before: End Protocols
|
||||
```
|
||||
|
||||
5. 将该函数加入 `unilabos/compile/__init__.py` 的 `action_protocol_generators` 中:
|
||||
```{literalinclude} ../../unilabos/compile/__init__.py
|
||||
:start-after: Define
|
||||
:end-before: End Protocols
|
||||
```
|
||||
|
||||
6. 记得将新开发的 `Protocol` 添加至启动时的 `devices.json` 中。
|
||||
```{literalinclude} ../../devices.json
|
||||
:lines: 2-4
|
||||
```
|
||||
95
docs/developer_guide/add_yaml.md
Normal file
95
docs/developer_guide/add_yaml.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# yaml注册表编写指南
|
||||
|
||||
`注册表的结构`
|
||||
|
||||
1. 顶层名称:每个设备的注册表以设备名称开头,例如 new_device。
|
||||
2. class 字段:定义设备的模块路径和类型。
|
||||
3. schema 字段:定义设备的属性模式,包括属性类型、描述和必需字段。
|
||||
4. action_value_mappings 字段:定义设备支持的动作及其目标、反馈和结果。
|
||||
|
||||
`创建新的注册表教程`
|
||||
1. 创建文件
|
||||
在 devices 文件夹中创建一个新的 YAML 文件,例如 new_device.yaml。
|
||||
|
||||
2. 定义设备名称
|
||||
在文件中定义设备的顶层名称,例如:new_device
|
||||
|
||||
3. 定义设备的类信息
|
||||
添加设备的模块路径和类型:
|
||||
|
||||
```python
|
||||
new_device: # 定义一个名为 linear_motion.grbl 的设备
|
||||
|
||||
|
||||
class: # 定义设备的类信息
|
||||
module: unilabos.devices_names.new_device:NewDeviceClass # 指定模块路径和类名
|
||||
type: python # 指定类型为 Python 类
|
||||
status_types:
|
||||
```
|
||||
4. 定义设备支持的动作
|
||||
添加设备支持的动作及其目标、反馈和结果:
|
||||
```python
|
||||
action_value_mappings:
|
||||
set_speed:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: speed
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
```
|
||||
`如何编写action_valve_mappings`
|
||||
1. 在 devices 文件夹中的 YAML 文件中,action_value_mappings 是用来定义设备支持的动作(actions)及其目标值(goal)、反馈值(feedback)和结果值(result)的映射规则。以下是规则和编写方法:
|
||||
```python
|
||||
action_value_mappings:
|
||||
<action_name>: # <action_name>:动作的名称
|
||||
# start:启动设备或某个功能。
|
||||
# stop:停止设备或某个功能。
|
||||
# set_speed:设置设备的速度。
|
||||
# set_temperature:设置设备的温度。
|
||||
# move_to_position:移动设备到指定位置。
|
||||
# stir:执行搅拌操作。
|
||||
# heatchill:执行加热或冷却操作。
|
||||
# send_nav_task:发送导航任务(例如机器人导航)。
|
||||
# set_timer:设置设备的计时器。
|
||||
# valve_open_cmd:打开阀门。
|
||||
# valve_close_cmd:关闭阀门。
|
||||
# execute_command_from_outer:执行外部命令。
|
||||
# push_to:控制设备推送到某个位置(例如机械爪)。
|
||||
# move_through_points:导航设备通过多个点。
|
||||
|
||||
type: <ActionType> # 动作的类型,表示动作的功能
|
||||
# 根据动作的功能选择合适的类型:
|
||||
# SendCmd:发送简单命令。
|
||||
# NavigateThroughPoses:导航动作。
|
||||
# SingleJointPosition:设置单一关节的位置。
|
||||
# Stir:搅拌动作。
|
||||
# HeatChill:加热或冷却动作。
|
||||
|
||||
goal: # 定义动作的目标值映射,表示需要传递给设备的参数。
|
||||
<goal_key>: <mapped_value> #确定设备需要的输入参数,并将其映射到设备的字段。
|
||||
|
||||
feedback: # 定义动作的反馈值映射,表示设备执行动作时返回的实时状态。
|
||||
<feedback_key>: <mapped_value>
|
||||
result: # 定义动作的结果值映射,表示动作完成后返回的最终结果。
|
||||
<result_key>: <mapped_value>
|
||||
```
|
||||
|
||||
6. 定义设备的属性模式
|
||||
添加设备的属性模式,包括属性类型和描述:
|
||||
```python
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
description: The status of the device
|
||||
speed:
|
||||
type: number
|
||||
description: The speed of the device
|
||||
required:
|
||||
- status
|
||||
- speed
|
||||
additionalProperties: false
|
||||
```
|
||||
# 写完yaml注册表后需要添加到哪些其他文件?
|
||||
330
docs/developer_guide/device_driver.md
Normal file
330
docs/developer_guide/device_driver.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# 设备 Driver 开发
|
||||
|
||||
我们对设备 Driver 的定义,是一个 Python/C++/C# 类,类的方法可以用于获取传感器数据、执行设备动作、更新物料信息。它们经过 Uni-Lab 的通信中间件包装,就能成为高效分布式通信的设备节点。
|
||||
|
||||
因此,若已有设备的 SDK (Driver),可以直接 [添加进 Uni-Lab](add_device.md)。仅当没有 SDK (Driver) 时,请参考本章作开发。
|
||||
|
||||
## 有串口字符串指令集文档的设备:Python 串口通信(常见 RS485, RS232, USB)
|
||||
|
||||
开发方式:对照厂家给出的指令集文档,实现相应发送指令字符串的 python 函数。可参考 [注射泵串口驱动样例](https://github.com/TablewareBox/runze-syringe-pump)
|
||||
|
||||
## 常见工业通信协议:Modbus(RTU, TCP)
|
||||
|
||||
Modbus 与 RS485、RS232 不一样的地方在于,会有更多直接寄存器的读写,以及涉及字节序转换(Big Endian, Little Endian)。
|
||||
|
||||
Uni-Lab 开发团队在仓库中提供了3个样例:
|
||||
|
||||
* 单一机械设备**电夹爪**,通讯协议可见 [增广夹爪通讯协议](https://doc.rmaxis.com/docs/communication/fieldbus/),驱动代码位于 `unilabos/devices/gripper/rmaxis_v4.py`
|
||||
* 单一通信设备**IO板卡**,驱动代码位于 `unilabos/device_comms/gripper/SRND_16_IO.py`
|
||||
* 执行多设备复杂任务逻辑的**PLC**,Uni-Lab 提供了基于地址表的接入方式和点动工作流编写,测试代码位于 `unilabos/device_comms/modbus_plc/test/test_workflow.py`
|
||||
|
||||
****
|
||||
|
||||
## 其他工业通信协议:CANopen, Ethernet, OPCUA...
|
||||
|
||||
【敬请期待】
|
||||
|
||||
## 没有接口的老设备老软件:使用 PyWinAuto
|
||||
|
||||
**pywinauto**是一个 Python 库,用于自动化Windows GUI操作。它可以模拟用户的鼠标点击、键盘输入、窗口操作等,广泛应用于自动化测试、GUI自动化等场景。它支持通过两个后端进行操作:
|
||||
|
||||
* **win32**后端:适用于大多数Windows应用程序,使用native Win32 API。(pywinauto_recorder默认使用win32后端)
|
||||
* **uia**后端:基于Microsoft UI Automation,适用于较新的应用程序,特别是基于WPF或UWP的应用程序。(在win10上,会有更全的目录,有的窗口win32会识别不到)
|
||||
|
||||
### windows平台安装pywinauto和pywinauto_recorder
|
||||
|
||||
直接安装会造成环境崩溃,需要下载并解压已经修改好的文件。
|
||||
|
||||
cd到对应目录,执行安装
|
||||
|
||||
`pip install . -i ``https://pypi.tuna.tsinghua.edu.cn/simple`
|
||||
|
||||

|
||||
|
||||
windows平台测试 python pywinauto_recorder.py,退出使用两次ctrl+alt+r取消选中,关闭命令提示符。
|
||||
|
||||
### 计算器例子
|
||||
|
||||
你可以先打开windows的计算器,然后在ilab的环境中运行下面的代码片段,可观察到得到结果,通过这一案例,你需要掌握的pywinauto用法:
|
||||
|
||||
* 连接到指定进程
|
||||
* 利用dump_tree查找需要的窗口
|
||||
* 获取某个位置的信息
|
||||
* 模拟点击
|
||||
* 模拟输入
|
||||
|
||||
#### 代码学习
|
||||
|
||||
```Python
|
||||
from pywinauto import Application
|
||||
import time
|
||||
|
||||
from pywinauto.findwindows import ElementAmbiguousError
|
||||
|
||||
# 启动计算器应用
|
||||
app = Application(backend='uia').connect(title="计算器")
|
||||
|
||||
# 连接到计算器窗口
|
||||
window = app["计算器Dialog0"]
|
||||
|
||||
# 打印窗口控件树结构,帮助定位控件
|
||||
window.dump_tree(depth=3)
|
||||
# 详细输出
|
||||
"""
|
||||
Dialog - '计算器' (L-419, T773, R-73, B1287)
|
||||
['计算器Dialog', 'Dialog', '计算器', '计算器Dialog0', '计算器Dialog1', 'Dialog0', 'Dialog1', '计算器0', '计算器1']
|
||||
child_window(title="计算器", control_type="Window")
|
||||
|
|
||||
| Dialog - '计算器' (L-269, T774, R-81, B806)
|
||||
| ['计算器Dialog2', 'Dialog2', '计算器2']
|
||||
| child_window(title="计算器", auto_id="TitleBar", control_type="Window")
|
||||
| |
|
||||
| | Menu - '系统' (L0, T0, R0, B0)
|
||||
| | ['Menu', '系统', '系统Menu', '系统0', '系统1']
|
||||
| | child_window(title="系统", auto_id="SystemMenuBar", control_type="MenuBar")
|
||||
| |
|
||||
| | Button - '最小化 计算器' (L-219, T774, R-173, B806)
|
||||
| | ['Button', '最小化 计算器Button', '最小化 计算器', 'Button0', 'Button1']
|
||||
| | child_window(title="最小化 计算器", auto_id="Minimize", control_type="Button")
|
||||
| |
|
||||
| | Button - '使 计算器 最大化' (L-173, T774, R-127, B806)
|
||||
| | ['Button2', '使 计算器 最大化', '使 计算器 最大化Button']
|
||||
| | child_window(title="使 计算器 最大化", auto_id="Maximize", control_type="Button")
|
||||
| |
|
||||
| | Button - '关闭 计算器' (L-127, T774, R-81, B806)
|
||||
| | ['Button3', '关闭 计算器Button', '关闭 计算器']
|
||||
| | child_window(title="关闭 计算器", auto_id="Close", control_type="Button")
|
||||
|
|
||||
| Dialog - '计算器' (L-411, T774, R-81, B1279)
|
||||
| ['计算器Dialog3', 'Dialog3', '计算器3']
|
||||
| child_window(title="计算器", control_type="Window")
|
||||
| |
|
||||
| | Static - '计算器' (L-363, T782, R-327, B798)
|
||||
| | ['计算器Static', 'Static', '计算器4', 'Static0', 'Static1']
|
||||
| | child_window(title="计算器", auto_id="AppName", control_type="Text")
|
||||
| |
|
||||
| | Custom - '' (L-411, T806, R-81, B1279)
|
||||
| | ['Custom', '计算器Custom']
|
||||
| | child_window(auto_id="NavView", control_type="Custom")
|
||||
|
|
||||
| Pane - '' (L-411, T806, R-81, B1279)
|
||||
| ['Pane', '计算器Pane']
|
||||
"""
|
||||
|
||||
# 通过控件路径(可参考下一小节路径)可以发现,本文档第48-50行是我们需要定位的控件
|
||||
# 本文档第16-18行为其自身,即depth=1,我们要定位的第48-50行是depth=3的控件,从树来一级一级定位即可
|
||||
# PyWinAuto为我们提供了非常便捷的取窗口方式,在每3行dump的内容中,第三行就是从上一级取出当前窗口的方式,直接复制即可
|
||||
# 这里注意到,使用title="计算器", control_type="Window"进行匹配,会出现两个,因此指定found_index=1
|
||||
sub_window = window.child_window(title="计算器", control_type="Window", found_index=1)
|
||||
target_window = sub_window.child_window(auto_id="NavView", control_type="Custom")
|
||||
target_window.dump_tree(depth=3)
|
||||
"""
|
||||
Custom - '' (L-411, T806, R-81, B1279)
|
||||
['标准Custom', 'Custom']
|
||||
child_window(auto_id="NavView", control_type="Custom")
|
||||
|
|
||||
| Button - '打开导航' (L-407, T812, R-367, B848)
|
||||
| ['打开导航Button', '打开导航', 'Button', 'Button0', 'Button1']
|
||||
| child_window(title="打开导航", auto_id="TogglePaneButton", control_type="Button")
|
||||
| |
|
||||
| | Static - '' (L0, T0, R0, B0)
|
||||
| | ['Static', 'Static0', 'Static1']
|
||||
| | child_window(auto_id="PaneTitleTextBlock", control_type="Text")
|
||||
|
|
||||
| GroupBox - '' (L-411, T814, R-81, B1275)
|
||||
| ['标准GroupBox', 'GroupBox', 'GroupBox0', 'GroupBox1']
|
||||
| |
|
||||
| | Static - '表达式为 ' (L0, T0, R0, B0)
|
||||
| | ['表达式为 ', 'Static2', '表达式为 Static']
|
||||
| | child_window(title="表达式为 ", auto_id="CalculatorExpression", control_type="Text")
|
||||
| |
|
||||
| | Static - '显示为 0' (L-411, T875, R-81, B947)
|
||||
| | ['显示为 0Static', '显示为 0', 'Static3']
|
||||
| | child_window(title="显示为 0", auto_id="CalculatorResults", control_type="Text")
|
||||
| |
|
||||
| | Button - '打开历史记录浮出控件' (L-121, T814, R-89, B846)
|
||||
| | ['打开历史记录浮出控件', '打开历史记录浮出控件Button', 'Button2']
|
||||
| | child_window(title="打开历史记录浮出控件", auto_id="HistoryButton", control_type="Button")
|
||||
| |
|
||||
| | GroupBox - '记忆控件' (L-407, T948, R-85, B976)
|
||||
| | ['记忆控件', '记忆控件GroupBox', 'GroupBox2']
|
||||
| | child_window(title="记忆控件", auto_id="MemoryPanel", control_type="Group")
|
||||
| |
|
||||
| | GroupBox - '显示控件' (L-407, T978, R-85, B1026)
|
||||
| | ['显示控件', 'GroupBox3', '显示控件GroupBox']
|
||||
| | child_window(title="显示控件", auto_id="DisplayControls", control_type="Group")
|
||||
| |
|
||||
| | GroupBox - '标准函数' (L-407, T1028, R-166, B1076)
|
||||
| | ['标准函数', '标准函数GroupBox', 'GroupBox4']
|
||||
| | child_window(title="标准函数", auto_id="StandardFunctions", control_type="Group")
|
||||
| |
|
||||
| | GroupBox - '标准运算符' (L-164, T1028, R-85, B1275)
|
||||
| | ['标准运算符', '标准运算符GroupBox', 'GroupBox5']
|
||||
| | child_window(title="标准运算符", auto_id="StandardOperators", control_type="Group")
|
||||
| |
|
||||
| | GroupBox - '数字键盘' (L-407, T1078, R-166, B1275)
|
||||
| | ['GroupBox6', '数字键盘', '数字键盘GroupBox']
|
||||
| | child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||
| |
|
||||
| | Button - '正负' (L-407, T1228, R-328, B1275)
|
||||
| | ['Button32', '正负Button', '正负']
|
||||
| | child_window(title="正负", auto_id="negateButton", control_type="Button")
|
||||
|
|
||||
| Static - '标准' (L-363, T815, R-322, B842)
|
||||
| ['标准', '标准Static', 'Static4']
|
||||
| child_window(title="标准", auto_id="Header", control_type="Text")
|
||||
|
|
||||
| Button - '始终置顶' (L-312, T814, R-280, B846)
|
||||
| ['始终置顶Button', '始终置顶', 'Button33']
|
||||
| child_window(title="始终置顶", auto_id="NormalAlwaysOnTopButton", control_type="Button")
|
||||
"""
|
||||
# 观察到GroupBox控件并没有提供默认的child_window,而list中的identifier均可作为best_match来索引
|
||||
# ['标准GroupBox', 'GroupBox', 'GroupBox0', 'GroupBox1'] 这里选用第0项
|
||||
group_box = target_window.child_window(best_match="标准GroupBox")
|
||||
numpad = group_box.child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||
numpad.dump_tree(depth=2)
|
||||
"""
|
||||
GroupBox - '数字键盘' (L-334, T1350, R-93, B1547)
|
||||
['GroupBox', '数字键盘', '数字键盘GroupBox']
|
||||
child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||
|
|
||||
| Button - '零' (L-253, T1500, R-174, B1547)
|
||||
| ['零Button', 'Button', '零', 'Button0', 'Button1']
|
||||
| child_window(title="零", auto_id="num0Button", control_type="Button")
|
||||
|
|
||||
| Button - '一' (L-334, T1450, R-255, B1498)
|
||||
| ['一Button', 'Button2', '一']
|
||||
| child_window(title="一", auto_id="num1Button", control_type="Button")
|
||||
|
|
||||
| Button - '二' (L-253, T1450, R-174, B1498)
|
||||
| ['Button3', '二', '二Button']
|
||||
| child_window(title="二", auto_id="num2Button", control_type="Button")
|
||||
|
|
||||
| Button - '三' (L-172, T1450, R-93, B1498)
|
||||
| ['Button4', '三', '三Button']
|
||||
| child_window(title="三", auto_id="num3Button", control_type="Button")
|
||||
|
|
||||
| Button - '四' (L-334, T1400, R-255, B1448)
|
||||
| ['四', 'Button5', '四Button']
|
||||
| child_window(title="四", auto_id="num4Button", control_type="Button")
|
||||
|
|
||||
| Button - '五' (L-253, T1400, R-174, B1448)
|
||||
| ['Button6', '五Button', '五']
|
||||
| child_window(title="五", auto_id="num5Button", control_type="Button")
|
||||
|
|
||||
| Button - '六' (L-172, T1400, R-93, B1448)
|
||||
| ['六Button', 'Button7', '六']
|
||||
| child_window(title="六", auto_id="num6Button", control_type="Button")
|
||||
|
|
||||
| Button - '七' (L-334, T1350, R-255, B1398)
|
||||
| ['Button8', '七Button', '七']
|
||||
| child_window(title="七", auto_id="num7Button", control_type="Button")
|
||||
|
|
||||
| Button - '八' (L-253, T1350, R-174, B1398)
|
||||
| ['八', 'Button9', '八Button']
|
||||
| child_window(title="八", auto_id="num8Button", control_type="Button")
|
||||
|
|
||||
| Button - '九' (L-172, T1350, R-93, B1398)
|
||||
| ['Button10', '九', '九Button']
|
||||
| child_window(title="九", auto_id="num9Button", control_type="Button")
|
||||
|
|
||||
| Button - '十进制分隔符' (L-172, T1500, R-93, B1547)
|
||||
| ['十进制分隔符Button', 'Button11', '十进制分隔符']
|
||||
| child_window(title="十进制分隔符", auto_id="decimalSeparatorButton", control_type="Button")
|
||||
"""
|
||||
# 获取按钮 '9'
|
||||
button_9 = numpad.child_window(title="九", auto_id="num9Button", control_type="Button")
|
||||
# 利用相同的办法,我们也可以找到增加和等于号的控件
|
||||
std_calc_panel = group_box.child_window(title="标准运算符", auto_id="StandardOperators", control_type="Group")
|
||||
equal_operation = std_calc_panel.child_window(title="等于", auto_id="equalButton", control_type="Button")
|
||||
|
||||
# 模拟点击按钮 '9'
|
||||
button_9.click_input()
|
||||
# 键入:https://github.com/pywinauto/pywinauto/blob/atspi/pywinauto/windows/keyboard.py
|
||||
# 模拟输入 '加号' 和 数字9
|
||||
window.type_keys("{VK_ADD}9")
|
||||
# 等于
|
||||
equal_operation.click_input()
|
||||
# 获取计算结果文本(显示在计算器窗口的文本框中)
|
||||
result = group_box.child_window(auto_id="CalculatorResults", control_type="Text").window_text()
|
||||
print(f"计算结果:{result[4:]}") # 应当得到结果18
|
||||
```
|
||||
|
||||
#### 依据像素判定状态
|
||||
|
||||
```Python
|
||||
# 有时,你需要根据窗口的颜色判断是否可用,是否正在运行,可以使用pyautogui来实现这一功能
|
||||
# pyautogui需要对应在环境中进行安装
|
||||
point_x = int(control_view.rectangle().left + control_view.rectangle().width() * 0.15)
|
||||
point_y = 15 + control_view.rectangle().top
|
||||
r, g, b = pyautogui.pixel(point_x, point_y)
|
||||
```
|
||||
|
||||
### pywinauto_recorder
|
||||
|
||||
pywinauto_recorder是一个配合 pywinauto 使用的工具,用于录制用户的操作,并生成相应的 pywinauto 脚本。这对于一些暂时无法直接调用DLL的函数并且需要模拟用户操作的场景非常有用。同时,可以省去仅用pywinauto的一些查找UI步骤。
|
||||
|
||||
#### 运行尝试
|
||||
|
||||
请参照 上手尝试-环境创建-3 开启pywinauto_recorder
|
||||
|
||||
例如我们这里先启动一个windows自带的计算器软件
|
||||
|
||||

|
||||
|
||||
在录制状态下点击数字键盘的“9”,随后退出录制,得到下图运行的文件。
|
||||
|
||||

|
||||
|
||||
```Python
|
||||
# encoding: utf-8
|
||||
|
||||
from pywinauto_recorder.player import *
|
||||
|
||||
with UIPath(u"计算器||Window"):
|
||||
with UIPath(u"计算器||Window->||Custom->||Group->数字键盘||Group"):
|
||||
click(u"九||Button")
|
||||
```
|
||||
|
||||
执行该python脚本,可以观察到新开启的计算器被点击了数字9
|
||||
|
||||

|
||||
|
||||
### `dump_tree`详解
|
||||
|
||||
`dump_tree`方法用于打印控件树结构,帮助我们快速了解应用程序窗口中的控件层级,尤其是在自动化测试或脚本开发中,识别控件非常重要。
|
||||
|
||||
```Python
|
||||
window.dump_tree(depth=[int类型数字], filename=None)
|
||||
# 打印当前窗口及其子控件的树结构
|
||||
# 在debug的过程中,如果需要查找某一控件,可以通过depth指定为4~5,利用搜索查看是哪个独立控件
|
||||
# 指定filename后将保存到对应目录文件中
|
||||
```
|
||||
|
||||
输出会列出窗口的各个控件及其子控件,显示每个控件的属性(如标题、类型等)。
|
||||
|
||||
```Python
|
||||
"""
|
||||
GroupBox - '数字键盘' (L-334, T1350, R-93, B1547)
|
||||
['GroupBox', '数字键盘', '数字键盘GroupBox']
|
||||
child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||
|
|
||||
| Button - '零' (L-253, T1500, R-174, B1547)
|
||||
| ['零Button', 'Button', '零', 'Button0', 'Button1']
|
||||
| child_window(title="零", auto_id="num0Button", control_type="Button")
|
||||
"""
|
||||
```
|
||||
|
||||
这里以上面计算器的例子对dump_tree进行解读
|
||||
|
||||
2~4行为当前对象的窗口
|
||||
|
||||
* 第2行分别是窗体的类型 `GroupBox`,窗体的题目 `数字键盘`,窗体的矩形区域坐标,对应的是屏幕上的位置(左、上、右、下)
|
||||
* 第3行是 `['GroupBox', '数字键盘', '数字键盘GroupBox']`,为控件的标识符列表,可以选择任意一个,使用 `child_window(best_match="标识符")`来获取该窗口
|
||||
* 第4行是获取该控件的方法,请注意该方法不能保证获取唯一,`title`如果是变化的,也需要删除 `title`参数
|
||||
|
||||
6~8行为当前对象窗口所包含的子窗口信息,信息类型对应2~4行
|
||||
|
||||
### 窗口获取注意事项
|
||||
|
||||
1. 在 `child_window`的时候,并不会立刻报错,只有在执行窗口的信息获取时才会调用,查询窗口是否存在,因此要想确定 `child_window`是否正确,可以调用子窗口对象的属性 `element_info`,来保证窗口存在
|
||||
BIN
docs/developer_guide/image/device_driver/calculator_01.png
Normal file
BIN
docs/developer_guide/image/device_driver/calculator_01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/developer_guide/image/device_driver/calculator_02.png
Normal file
BIN
docs/developer_guide/image/device_driver/calculator_02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/developer_guide/image/device_driver/calculator_03.png
Normal file
BIN
docs/developer_guide/image/device_driver/calculator_03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
docs/developer_guide/image/device_driver/pywinauto_install.png
Normal file
BIN
docs/developer_guide/image/device_driver/pywinauto_install.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
Reference in New Issue
Block a user