Robot Model

In this example, you will define a robot visualization model, see Fig. 17 and synchronize the robot position.

The robot visualization first pose

Fig. 17 The robot visualization first pose

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 visualization second pose

Fig. 18 The robot visualization second pose

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()