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:
- Conectar el PC al puerto de servicio del IRC5 (IP
192.168.125.1). - Cargar en el controlador el módulo RAPID
SocketJointControl. - Crear en Visual Studio Code un script Python con la GUI de sliders.
- Ejecutar el programa RAPID (modo AUTO) y luego el script Python.
- Desde la interfaz gráfica, pulsar Connect y mover los sliders para desplazar el IRB 140.
- Establecer una comunicación TCP/IP entre el PC y el controlador ABB IRC5.
- Controlar las 6 articulaciones del IRB 140 mediante sliders en una interfaz gráfica hecha en Python.
- Modificar la velocidad TCP del movimiento del robot usando un slider dedicado.
- Implementar un comando Home que envía al robot a una posición conjunta de referencia.
- 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.
- 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).
- IP del controlador:
-
Conecta un cable Ethernet entre el PC y el puerto de servicio del IRC5.
-
Configura la tarjeta de red del PC con una IP estática en la subred
192.168.125.x. -
Verifica conectividad con un ping desde el PC al controlador:
ping 192.168.125.1
-
Abre RobotStudio, conéctate al controlador real (
Online > Connect to Controller) y confirma que el robot está accesible.
- Abre Visual Studio Code.
- Crea una carpeta para tu proyecto (por ejemplo,
abb_irb140_sliders). - Dentro de esa carpeta, crea un nuevo archivo llamado, por ejemplo:
irb140_slider_control.py - 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.
- Abre RobotStudio y conéctate al controlador real IRC5 (Online → Connect to Controller).
- Ve al editor RAPID del task donde controlas el IRB 140 (por ejemplo,
T_ROB1). - Crea un nuevo módulo llamado
SocketJointControl(o edita uno con ese nombre). - 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
- Guarda los cambios y verifica que el módulo compile sin errores.
- Asegúrate de que el procedimiento
maindeSocketJointControlesté disponible para ejecutarse en el task.
-
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
maindeSocketJointControl. - Deberías ver en el FlexPendant mensajes como:
"Creando socket...""Haciendo bind al puerto 5000...""Esperando conexion del PC..."
-
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.
-
-
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.".
-
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.
-
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_tcpy se muestra"Velocidad TCP actualizada a:"con el valor.
-
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
jointtargetal robot y el IRB 140 se mueve a esa postura conjunta de referencia.
-
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á.
- En la GUI puedes pulsar “Enviar QUIT (salir RAPID loop)” para que el bucle RAPID salga de
- Puedes ajustar los límites articulares
JOINT_LIMITSy la posturaHOME_JOINTSa la configuración real de tu IRB 140. - El retardo
self._send_delay_mscontrola 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.