Robot Model
In this example, you will define a robot visualization model, see Fig. 17 and synchronize the robot position.
Run the Simulation
Open a terminal in VS Code and start the robot.py Python script with the following command:
python models/robot/robot.py
Start the program.py Python script which moves the robot with the following command:
python models/robot/program.py
Open localhost:8077 in your web browser to display the simulation as shown in Fig. 17. After the robot reached the first pose, the Python script waits until you press the Enter key. After you have pressed the Enter key, the robot moves to the second pose as shown in Fig. 18.
The Robot Python File
The Python file models/robot/robot.py defines the robot visualization model.
1"""Describes a robot visualization model."""
2
3from contextlib import contextmanager
4from dataclasses import dataclass
5from pathlib import Path
6from typing import Generator
7
8from asyncua import ua
9from asyncua.sync import Client as UAClient
10from voraus_3d_visu import Visu
11
12from voraus_simulation import Pose, traits, transforms
13
14models = Path(__file__).parent
15
16
17@dataclass
18class RobotData:
19 """Describes the robot data."""
20
21 joint_positions: list[float]
22 tcp_position: list[float]
23 tcp_quaternion: list[float]
24 world_position: list[float]
25 world_quaternion: list[float]
26 digital_outputs: list[bool]
27
28
29class RobotClient:
30 """Describes a robot client."""
31
32 def __init__(self, client: UAClient) -> None:
33 """Initializes a robot client.
34
35 Args:
36 client: The OPC UA client to use.
37 """
38 self.client = client
39 self.joint_positions = self.client.get_node("ns=1;i=100111")
40 self.tcp_position = self.client.get_node("ns=1;i=100707")
41 self.tcp_quaternion = self.client.get_node("ns=1;i=100710")
42 self.digital_outputs = self.client.get_node("ns=1;i=202202")
43 self.commands = self.client.get_node("ns=1;i=100240")
44 self.set_digital_output = self.client.get_node("ns=1;i=100204")
45
46
47class Robot(traits.VisuTraits):
48 """Defines a robot visualization model."""
49
50 def __init__(self, url: str, position: list[float] | None = None, rotation: list[float] | None = None) -> None:
51 """Initializes a robot visualization model.
52
53 Args:
54 url: The OPC UA URL.
55 position: The initial position. Defaults to None.
56 rotation: The initial rotation. Defaults to None.
57 """
58 super().__init__()
59 _position: list[float] = [0, 0, 0] if position is None else position
60 _rotation: list[float] = [0, 0, 0] if rotation is None else rotation
61
62 glb_path = models / "robot.glb"
63
64 quaternion = transforms.quaternion_from_euler(_rotation)
65 self.load_glb(glb_path, Pose(position=_position, orientation=quaternion))
66
67 self.origin = Pose(position=_position, orientation=quaternion)
68
69 self._robot_data: RobotData | None = None
70 self._ua_client = UAClient(url)
71 self._robot_client: RobotClient | None = None
72
73 @property
74 def robot_data(self) -> RobotData:
75 """Makes sure robot data is not None.
76
77 Returns:
78 The robot data.
79 """
80 assert self._robot_data is not None, "Robot data not updated."
81 return self._robot_data
82
83 @property
84 def robot_client(self) -> RobotClient:
85 """Makes sure robot data is not None.
86
87 Returns:
88 The robot client.
89 """
90 assert self._robot_client is not None, "Robot client not connected."
91 return self._robot_client
92
93 @contextmanager
94 def connection(self) -> Generator[None, None, None]:
95 """A robot connection context manager.
96
97 Yields:
98 None
99 """
100 with self._ua_client:
101 self._robot_client = RobotClient(self._ua_client)
102 self.get_robot_data()
103 yield
104
105 def get_robot_data(self) -> RobotData:
106 """Reads the robot data via OPC UA.
107
108 Returns:
109 The robot data.
110 """
111 joint_positions: list[float]
112 tcp_position: list[float]
113 wxyz_quaternion: list[float]
114 digital_outputs: list[bool]
115 joint_positions, tcp_position, wxyz_quaternion, digital_outputs = self.robot_client.client.read_values(
116 [
117 self.robot_client.joint_positions,
118 self.robot_client.tcp_position,
119 self.robot_client.tcp_quaternion,
120 self.robot_client.digital_outputs,
121 ]
122 )
123 w, x, y, z = wxyz_quaternion
124 xyzw_quaternion = [x, y, z, w]
125 world_pose = transforms.multiply(self.origin, Pose(position=tcp_position[:3], orientation=xyzw_quaternion))
126
127 self._robot_data = RobotData(
128 joint_positions=joint_positions,
129 tcp_position=tcp_position[:3],
130 tcp_quaternion=xyzw_quaternion,
131 world_position=world_pose.position,
132 world_quaternion=world_pose.orientation,
133 digital_outputs=digital_outputs,
134 )
135
136 return self.robot_data
137
138 def set_digital_output(self, index: int, value: bool) -> None:
139 """Sets a digital output.
140
141 Args:
142 index: The index of the output.
143 value: The value to set.
144 """
145 self.robot_client.commands.call_method(
146 self.robot_client.set_digital_output.nodeid,
147 ua.Variant(index + 1, ua.VariantType.UInt32),
148 ua.Variant(value, ua.VariantType.Boolean),
149 )
150
151 def get_visu_data(self) -> list:
152 """Returns the visualization data for the robot joint positions.
153
154 Returns:
155 The updates for the robot joint positions.
156 """
157 return [
158 self.visu_object.child("CS0").rotation.z(self.robot_data.joint_positions[0]),
159 self.visu_object.child("CS1").rotation.z(self.robot_data.joint_positions[1]),
160 self.visu_object.child("CS2").rotation.z(self.robot_data.joint_positions[2]),
161 self.visu_object.child("CS3").rotation.z(self.robot_data.joint_positions[3]),
162 self.visu_object.child("CS4").rotation.z(self.robot_data.joint_positions[4]),
163 self.visu_object.child("CS5").rotation.z(self.robot_data.joint_positions[5]),
164 ]
165
166
167if __name__ == "__main__":
168 from math import radians
169
170 from voraus_simulation import Simulation
171
172 simulation = Simulation(frequency=50, visualization=Visu("http://voraus-3d-visu/"))
173 robot = Robot("opc.tcp://voraus-core:48401/", [1.6, 0.1, 0.3], rotation=[0, 0, radians(-35)])
174
175 with simulation.run(), robot.connection():
176 while True:
177 robot.get_robot_data()
178 simulation.step()
179 simulation.sleep()
The Program Python File
The Python file models/robot/program.py defines the robot program.
1"""Moves the robot."""
2
3from math import radians
4
5from voraus_robot_arm import CartesianPose, JointPose, VorausIndustrialRobotArm
6
7HOME = JointPose(0, -1.57, 1.57, -1.57, -1.57, 0)
8POSE1 = CartesianPose(0.5, 0, 0.2, -radians(180), 0, radians(35))
9
10if __name__ == "__main__":
11 robot = VorausIndustrialRobotArm()
12 with robot.connect("voraus-core", 48401):
13 robot.enable()
14 robot.move_ptp(HOME).result()
15 input("Press <enter> to move the robot again.")
16 robot.move_linear(POSE1).result()