Skip to content

labsir-un/Control-del-robot-ABB-IRB-140-v-a-TCP-IP-desde-Python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 

Repository files navigation

Escudo UNAL

Curso de Robótica 2025-II

Introducción a Robots Phantom Pincher X100

Control del robot ABB IRB 140 vía TCP/IP desde Python

Pedro Fabián Cárdenas Herrera
Manuel Felipe Carranza Montenegro

Introducción

Esta guía complementa el trabajo realizado con los robots PhantomX y muestra cómo controlar un robot ABB IRB 140 con controlador IRC5 real usando:

  • Un módulo RAPID que abre un socket TCP en el IRC5.
  • Una interfaz gráfica en Python (Tkinter) con sliders para mover cada articulación y cambiar la velocidad del movimiento.

El flujo general es:

  1. Conectar el PC al puerto de servicio del IRC5 (IP 192.168.125.1).
  2. Cargar en el controlador el módulo RAPID SocketJointControl.
  3. Crear en Visual Studio Code un script Python con la GUI de sliders.
  4. Ejecutar el programa RAPID (modo AUTO) y luego el script Python.
  5. Desde la interfaz gráfica, pulsar Connect y mover los sliders para desplazar el IRB 140.

Objetivos

  1. Establecer una comunicación TCP/IP entre el PC y el controlador ABB IRC5.
  2. Controlar las 6 articulaciones del IRB 140 mediante sliders en una interfaz gráfica hecha en Python.
  3. Modificar la velocidad TCP del movimiento del robot usando un slider dedicado.
  4. Implementar un comando Home que envía al robot a una posición conjunta de referencia.
  5. Practicar la integración PC–robot combinando programación RAPID y Python.

⚠️ Advertencia de seguridad:
Todas las pruebas deben realizarse en un entorno seguro, con el área del robot despejada y respetando los procedimientos de seguridad de laboratorio.


Prerrequisitos

  • Controlador ABB IRC5 con robot IRB 140 real.
  • Software RobotStudio instalado para conectarse al controlador.
  • PC con:
    • Python 3 instalado.
    • Soporte para Tkinter (viene por defecto en la mayoría de instalaciones).
    • Visual Studio Code u otro editor de texto/código.
  • Conexión Ethernet desde el PC al puerto de servicio del IRC5:
    • IP del controlador: 192.168.125.1 (por defecto).
    • La interfaz de red del PC debe estar en la misma subred (por ejemplo, 192.168.125.2/255.255.255.0).

Preparativos de hardware y red

  1. Conecta un cable Ethernet entre el PC y el puerto de servicio del IRC5.

  2. Configura la tarjeta de red del PC con una IP estática en la subred 192.168.125.x.

  3. Verifica conectividad con un ping desde el PC al controlador:

    ping 192.168.125.1
  4. Abre RobotStudio, conéctate al controlador real (Online > Connect to Controller) y confirma que el robot está accesible.


1. Crear el script Python en Visual Studio Code

1.1. Crear archivo y pegar el código

  1. Abre Visual Studio Code.
  2. Crea una carpeta para tu proyecto (por ejemplo, abb_irb140_sliders).
  3. Dentro de esa carpeta, crea un nuevo archivo llamado, por ejemplo:
    irb140_slider_control.py
  4. Copia y pega tal cual el siguiente código en ese archivo:
import socket
import tkinter as tk
from tkinter import ttk, messagebox

ROBOT_IP = "192.168.125.1"   # IP del IRC5 (puerto de servicio)
ROBOT_PORT = 5000            # Debe coincidir con el puerto de SocketBind en RAPID

# Límites típicos para IRB140 (ajusta a tu instalación si es necesario)
JOINT_LIMITS = [
    (-165, 165),   # J1
    (-110, 110),   # J2
    (-110, 70),    # J3
    (-200, 200),   # J4
    (-115, 115),   # J5
    (-400, 400),   # J6
]

# Posición "home" ejemplo (modifícala a tu home real)
HOME_JOINTS = [0, 0, 0, 0, 0, 0]

# Rango de velocidad en mm/s (para dynSpeed.v_tcp)
SPEED_MIN = 10.0
SPEED_MAX = 1000.0
DEFAULT_SPEED = 100.0


class IRC5SliderGUI:
    def __init__(self, master):
        self.master = master
        self.master.title("Control IRB140 por Sliders (IRC5)")
        self.sock = None

        self.joint_vars = []

        # Debounce para jointtargets (solo última posición)
        self._send_after_id = None
        self._send_delay_ms = 150  # milisegundos

        # Velocidad
        self.speed_var = tk.DoubleVar(value=DEFAULT_SPEED)
        self._speed_after_id = None
        self._speed_delay_ms = 150  # milisegundos

        self._build_style()
        self._build_ui()
        self.master.protocol("WM_DELETE_WINDOW", self.on_close)

    # ------------------------------------------------------------------ UI
    def _build_style(self):
        style = ttk.Style(self.master)
        try:
            style.theme_use("clam")
        except tk.TclError:
            pass

        style.configure("TFrame", background="#1e1e1e")
        style.configure(
            "Header.TLabel",
            background="#1e1e1e",
            foreground="#ffffff",
            font=("Segoe UI", 16, "bold"),
        )
        style.configure(
            "SubHeader.TLabel",
            background="#1e1e1e",
            foreground="#bbbbbb",
            font=("Segoe UI", 10),
        )
        style.configure(
            "Status.TLabel",
            background="#1e1e1e",
            foreground="#ff5555",
            font=("Segoe UI", 10, "bold"),
        )
        style.configure("TButton", font=("Segoe UI", 10, "bold"), padding=6)
        style.configure("Home.TButton", font=("Segoe UI", 10, "bold"), padding=8)
        style.map(
            "Home.TButton",
            background=[("!disabled", "#007acc"), ("pressed", "#005999")],
            foreground=[("!disabled", "#ffffff")],
        )
        style.configure(
            "Slider.TLabel",
            background="#2d2d30",
            foreground="#ffffff",
            font=("Segoe UI", 10),
        )
        style.configure(
            "Value.TLabel",
            background="#2d2d30",
            foreground="#a0ffa0",
            font=("Consolas", 10),
        )
        style.configure(
            "Group.TLabelframe",
            background="#2d2d30",
            foreground="#ffffff",
            font=("Segoe UI", 11, "bold"),
        )
        style.configure(
            "Group.TLabelframe.Label",
            background="#2d2d30",
            foreground="#ffffff",
        )

    def _build_ui(self):
        self.master.configure(bg="#1e1e1e")

        main = ttk.Frame(self.master, padding=12)
        main.grid(row=0, column=0, sticky="nsew")
        self.master.rowconfigure(0, weight=1)
        self.master.columnconfigure(0, weight=1)

        # Header
        header_frame = ttk.Frame(main)
        header_frame.grid(row=0, column=0, columnspan=2, sticky="we")
        header_label = ttk.Label(
            header_frame,
            text="Control IRB140 vía TCP/IP",
            style="Header.TLabel",
        )
        header_label.grid(row=0, column=0, sticky="w")
        subtitle_label = ttk.Label(
            header_frame,
            text="Sliders → solo última posición · Velocidad · Home · QUIT",
            style="SubHeader.TLabel",
        )
        subtitle_label.grid(row=1, column=0, sticky="w", pady=(0, 8))

        # Conexión
        conn_frame = ttk.Frame(main, padding=(0, 8, 0, 8))
        conn_frame.grid(row=1, column=0, columnspan=2, sticky="we")
        conn_frame.columnconfigure(3, weight=1)

        ttk.Button(conn_frame, text="Connect", command=self.connect).grid(
            row=0, column=0, padx=4
        )
        ttk.Button(conn_frame, text="Disconnect", command=self.disconnect).grid(
            row=0, column=1, padx=4
        )

        self.status_label = ttk.Label(
            conn_frame, text="Desconectado", style="Status.TLabel"
        )
        self.status_label.grid(row=0, column=2, padx=10, sticky="w")

        ip_label = ttk.Label(
            conn_frame,
            text=f"Destino: {ROBOT_IP}:{ROBOT_PORT}",
            style="SubHeader.TLabel",
        )
        ip_label.grid(row=0, column=3, sticky="e")

        # Frame sliders de articulaciones
        sliders_frame = ttk.Labelframe(
            main,
            text=" Articulaciones (grados) ",
            style="Group.TLabelframe",
            padding=10,
        )
        sliders_frame.grid(row=2, column=0, sticky="nsew", pady=(4, 0))
        main.rowconfigure(2, weight=1)
        main.columnconfigure(0, weight=3)

        for i in range(6):
            jmin, jmax = JOINT_LIMITS[i]
            var = tk.DoubleVar(value=0.0)
            self.joint_vars.append(var)

            label = ttk.Label(
                sliders_frame,
                text=f"J{i+1}",
                style="Slider.TLabel",
            )
            label.grid(row=i, column=0, padx=(2, 6), pady=6, sticky="e")

            scale = tk.Scale(
                sliders_frame,
                from_=jmin,
                to=jmax,
                orient="horizontal",
                resolution=0.1,
                length=260,
                showvalue=0,
                variable=var,
                command=self.on_slider_change,  # usa debounce
                bg="#2d2d30",
                fg="#ffffff",
                highlightthickness=0,
                troughcolor="#3f3f46",
                sliderrelief="flat",
            )
            scale.grid(row=i, column=1, padx=4, pady=6, sticky="we")

            val_label = ttk.Label(
                sliders_frame,
                textvariable=self._make_rounded_var(var),
                style="Value.TLabel",
                width=8,
                anchor="w",
            )
            val_label.grid(row=i, column=2, padx=(8, 2), pady=6, sticky="w")

        sliders_frame.columnconfigure(1, weight=1)

        # Panel derecho: velocidad + controles
        control_frame = ttk.Labelframe(
            main,
            text=" Controles ",
            style="Group.TLabelframe",
            padding=10,
        )
        control_frame.grid(row=2, column=1, sticky="nsew", padx=(8, 0), pady=(4, 0))
        main.columnconfigure(1, weight=1)

        # Slider de velocidad
        speed_label = ttk.Label(
            control_frame,
            text="Velocidad TCP (mm/s)",
            style="Slider.TLabel",
        )
        speed_label.grid(row=0, column=0, sticky="w", pady=(0, 4))

        speed_scale = tk.Scale(
            control_frame,
            from_=SPEED_MIN,
            to=SPEED_MAX,
            orient="horizontal",
            resolution=1.0,
            length=220,
            showvalue=0,
            variable=self.speed_var,
            command=self.on_speed_change,
            bg="#2d2d30",
            fg="#ffffff",
            highlightthickness=0,
            troughcolor="#3f3f46",
            sliderrelief="flat",
        )
        speed_scale.grid(row=1, column=0, sticky="we", pady=(0, 4))

        speed_val_label = ttk.Label(
            control_frame,
            textvariable=self._make_rounded_var(self.speed_var),
            style="Value.TLabel",
            width=8,
            anchor="e",
        )
        speed_val_label.grid(row=1, column=1, padx=(6, 0), sticky="e")

        # Botones
        ttk.Button(
            control_frame,
            text="Ir a Home",
            style="Home.TButton",
            command=self.go_home,
        ).grid(row=2, column=0, columnspan=2, sticky="we", pady=(8, 6))

        ttk.Button(
            control_frame,
            text="Enviar QUIT (salir RAPID loop)",
            command=self.send_quit,
        ).grid(row=3, column=0, columnspan=2, sticky="we", pady=(0, 6))

        info_label = ttk.Label(
            control_frame,
            text=(
                "Sliders: se envía solo la última posición tras una pausa corta.\n"
                "Velocidad: envía SPD:<v> y actualiza dynSpeed.v_tcp en RAPID.\n"
                "Home manda directamente el jointtarget de referencia."
            ),
            style="SubHeader.TLabel",
            justify="left",
        )
        info_label.grid(row=4, column=0, columnspan=2, sticky="w", pady=(6, 0))

    def _make_rounded_var(self, var):
        s = tk.StringVar()
        s.set(f"{var.get():.1f}")

        def trace(*_):
            s.set(f"{var.get():.1f}")

        var.trace_add("write", trace)
        return s

    # ------------------------------------------------------------------ Conexión
    def connect(self):
        if self.sock is not None:
            messagebox.showinfo("Info", "Ya hay una conexión abierta.")
            return

        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(5.0)
            s.connect((ROBOT_IP, ROBOT_PORT))
        except OSError as e:
            messagebox.showerror("Error de conexión", f"No se pudo conectar: {e}")
            return

        self.sock = s
        self.status_label.configure(text="Conectado", foreground="#00ff7f")

        # Enviar velocidad inicial al conectar
        self.send_speed()

    def disconnect(self):
        if self.sock is not None:
            try:
                self.sock.sendall(b"QUIT")
            except OSError:
                pass
            try:
                self.sock.close()
            except OSError:
                pass
            self.sock = None

        self.status_label.configure(text="Desconectado", foreground="#ff5555")

    # ------------------------------------------------------------------ Envíos – JOINTS

    def on_slider_change(self, _value=None):
        """Se llama cada vez que cambias cualquier slider.
        Aquí sólo programamos el envío con retardo."""
        if self.sock is None:
            return

        if self._send_after_id is not None:
            self.master.after_cancel(self._send_after_id)

        self._send_after_id = self.master.after(
            self._send_delay_ms, self.send_current_joints
        )

    def get_current_joints(self):
        return [var.get() for var in self.joint_vars]

    def send_current_joints(self):
        """Envía el jointtarget actual al robot (se llama desde el after)."""
        self._send_after_id = None

        if self.sock is None:
            return

        j = self.get_current_joints()
        jt_str = (
            f"[[{j[0]:.3f},{j[1]:.3f},{j[2]:.3f},"
            f"{j[3]:.3f},{j[4]:.3f},{j[5]:.3f}],"
            f"[9E9,9E9,9E9,9E9,9E9,9E9]]"
        )

        try:
            self.sock.sendall(jt_str.encode("ascii"))
        except OSError as e:
            messagebox.showerror("Error de envío", f"No se pudo enviar: {e}")
            self.disconnect()

    # ------------------------------------------------------------------ Envíos – VELOCIDAD

    def on_speed_change(self, _value=None):
        """Debounce para cambios de velocidad."""
        if self.sock is None:
            return

        if self._speed_after_id is not None:
            self.master.after_cancel(self._speed_after_id)

        self._speed_after_id = self.master.after(
            self._speed_delay_ms, self.send_speed
        )

    def send_speed(self):
        """Envía SPD:<valor> al robot."""
        self._speed_after_id = None

        if self.sock is None:
            return

        v = self.speed_var.get()
        msg = f"SPD:{v:.1f}"

        try:
            self.sock.sendall(msg.encode("ascii"))
        except OSError as e:
            messagebox.showerror("Error de envío velocidad", f"No se pudo enviar: {e}")
            self.disconnect()

    # ------------------------------------------------------------------ Otros botones

    def go_home(self):
        """Pone los sliders en HOME y envía inmediatamente el jointtarget home."""
        if len(HOME_JOINTS) != 6:
            messagebox.showerror("Error", "HOME_JOINTS debe tener 6 valores.")
            return

        if self._send_after_id is not None:
            self.master.after_cancel(self._send_after_id)
            self._send_after_id = None

        for var, val in zip(self.joint_vars, HOME_JOINTS):
            var.set(val)

        self.send_current_joints()

    def send_quit(self):
        if self.sock is None:
            return
        try:
            self.sock.sendall(b"QUIT")
        except OSError:
            pass

    def on_close(self):
        if self._send_after_id is not None:
            self.master.after_cancel(self._send_after_id)
        if self._speed_after_id is not None:
            self.master.after_cancel(self._speed_after_id)
        self.disconnect()
        self.master.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    app = IRC5SliderGUI(root)
    root.mainloop()

Guarda el archivo irb140_slider_control.py.


2. Crear el módulo RAPID en RobotStudio (controlador real IRC5)

2.1. Conexión y creación de módulo

  1. Abre RobotStudio y conéctate al controlador real IRC5 (Online → Connect to Controller).
  2. Ve al editor RAPID del task donde controlas el IRB 140 (por ejemplo, T_ROB1).
  3. Crea un nuevo módulo llamado SocketJointControl (o edita uno con ese nombre).
  4. Pega tal cual el siguiente código RAPID dentro del módulo:
MODULE SocketJointControl

    VAR socketdev server_socket;
    VAR socketdev client_socket;

    VAR string cmd := "";

    VAR jointtarget jtFromPC :=
        [[0,0,0,0,0,0],
         [9E9,9E9,9E9,9E9,9E9,9E9]];

    ! Velocidad dinámica (se actualiza desde el PC con SPD:<valor>)
    VAR speeddata dynSpeed := [100, 500, 5000, 1000];

    PROC main()
        TPWrite "Creando socket...";
        SocketCreate server_socket;

        TPWrite "Haciendo bind al puerto 5000...";
        SocketBind server_socket, "", 5000;

        TPWrite "Escuchando conexiones entrantes...";
        SocketListen server_socket;

        WHILE TRUE DO
            TPWrite "Esperando conexion del PC...";
            SocketAccept server_socket, client_socket;
            TPWrite "PC conectado, entrando al bucle de lectura.";
            ReadLoop;
            TPWrite "PC desconectado.";
            SocketClose client_socket;
        ENDWHILE

        SocketClose server_socket;
    ENDPROC

    PROC ReadLoop()
        VAR string prefix;
        VAR string s_v;
        VAR num new_v;

        WHILE TRUE DO
            SocketReceive client_socket \Str:=cmd;

            ! -------------------------- QUIT --------------------------
            IF cmd = "QUIT" THEN
                TPWrite "Recibido QUIT, saliendo del bucle.";
                EXIT;
            ENDIF

            ! Prefijo para distinguir SPD:
            prefix := "";
            IF StrLen(cmd) >= 4 THEN
                prefix := StrPart(cmd, 1, 4);
            ENDIF

            ! ------------------- Comando de velocidad ------------------
            IF prefix = "SPD:" THEN
                ! Extraer la parte numérica después de "SPD:"
                s_v := StrPart(cmd, 5, StrLen(cmd) - 4);

                IF StrToVal(s_v, new_v) THEN
                    ! Limitar por seguridad
                    IF new_v < 10 THEN
                        new_v := 10;
                    ENDIF
                    IF new_v > 1000 THEN
                        new_v := 1000;
                    ENDIF

                    dynSpeed.v_tcp := new_v;

                    TPWrite "Velocidad TCP actualizada a:";
                    TPWrite ValToStr(new_v);

                ELSE
                    TPWrite "Error: no se pudo convertir SPD a numero.";
                    TPWrite cmd;
                ENDIF

            ! ---------------------- jointtarget -----------------------
            ELSEIF StrToVal(cmd, jtFromPC) THEN
                TPWrite "Recibido jointtarget, moviendo robot...";
                MoveAbsJ jtFromPC, dynSpeed, z50, tool0;

            ELSE
                TPWrite "Error: no se pudo convertir la cadena a jointtarget.";
                TPWrite cmd;
            ENDIF

        ENDWHILE
    ENDPROC

ENDMODULE
  1. Guarda los cambios y verifica que el módulo compile sin errores.
  2. Asegúrate de que el procedimiento main de SocketJointControl esté disponible para ejecutarse en el task.

3. Ejecución conjunta: RAPID + Python

3.1. Secuencia recomendada

  1. En RobotStudio / FlexPendant:

    • Pon el robot en modo AUTO (según las normas de la celda).
    • Selecciona el task donde está el módulo SocketJointControl.
    • Ejecuta el procedimiento main de SocketJointControl.
    • Deberías ver en el FlexPendant mensajes como:
      • "Creando socket..."
      • "Haciendo bind al puerto 5000..."
      • "Esperando conexion del PC..."
  2. En el PC (Python):

    • Abre una terminal en la carpeta donde está irb140_slider_control.py.

    • Ejecuta:

      python3 irb140_slider_control.py
    • Se abrirá la interfaz gráfica con los sliders.

  3. Conectar desde la GUI:

    • En la ventana de la GUI, pulsa el botón Connect.
    • Si la conexión es correcta, el estado deberá cambiar a “Conectado” en verde.
    • El RAPID escribirá "PC conectado, entrando al bucle de lectura.".
  4. Mover el robot con los sliders:

    • Mueve uno o varios sliders de las articulaciones J1…J6.
    • El sistema espera una pequeña pausa (≈150 ms) y luego envía solo la última posición al robot.
    • El IRB 140 se moverá hacia la combinación de ángulos indicada por los sliders.
  5. Cambiar la velocidad:

    • Usa el slider de “Velocidad TCP (mm/s)” para cambiar la velocidad.
    • Cada cambio envía un comando SPD:<valor> al IRC5.
    • En RAPID se actualiza dynSpeed.v_tcp y se muestra "Velocidad TCP actualizada a:" con el valor.
  6. Ir a Home:

    • Pulsa el botón “Ir a Home”.
    • Los sliders se ajustan a HOME_JOINTS = [0, 0, 0, 0, 0, 0].
    • Se envía ese jointtarget al robot y el IRB 140 se mueve a esa postura conjunta de referencia.
  7. Finalizar la sesión:

    • En la GUI puedes pulsar “Enviar QUIT (salir RAPID loop)” para que el bucle RAPID salga de ReadLoop.
    • Luego pulsa Disconnect (o simplemente cierra la ventana, que llama internamente a disconnect()).
    • En el FlexPendant verás “Recibido QUIT, saliendo del bucle.” y el socket se cerrará.

Notas finales

  • Puedes ajustar los límites articulares JOINT_LIMITS y la postura HOME_JOINTS a la configuración real de tu IRB 140.
  • El retardo self._send_delay_ms controla cuán “sensible” es la GUI a cambios rápidos de sliders:
    • Valores menores → más reactivo, pero envía más comandos.
    • Valores mayores → más suave, priorizando solo la última posición.
  • Asegúrate de respetar siempre las normas de seguridad del laboratorio y de tener una zona despejada antes de mover el robot desde el PC.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published