Saltar al contenido principal

Documentation Index

Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt

Use this file to discover all available pages before exploring further.

Esta página fue traducida automáticamente. Si encuentra errores o tiene sugerencias, contáctenos.
Esta guía cubre el ciclo de vida completo de una conexión WebSocket de Rhombus: establecer la conexión, realizar el handshake STOMP, mantener la conexión con heartbeats y manejar las desconexiones adecuadamente.

Descripción general del ciclo de vida

┌─────────────────┐
│  1. WebSocket    │
│     Handshake    │──── WSS + Auth Headers
└────────┬────────┘

┌─────────────────┐
│  2. STOMP        │
│     CONNECT      │──── Version + Heartbeat negotiation
└────────┬────────┘

┌─────────────────┐
│  3. SUBSCRIBE    │──── Topic subscription
└────────┬────────┘

┌─────────────────┐
│  4. MESSAGE      │◄─── Real-time events
│     Loop         │───► Heartbeat ping/pong
└────────┬────────┘

┌─────────────────┐
│  5. DISCONNECT   │──── Graceful shutdown
└─────────────────┘

Paso 1: handshake de WebSocket

Inicia una conexión WebSocket segura al endpoint de Rhombus con headers de autenticación:
import websocket

ws = websocket.create_connection(
    "wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token",
    header={"x-auth-apikey": API_TOKEN},
    timeout=10  # 10-second handshake timeout
)
Un handshake exitoso actualiza la conexión HTTP a WebSocket. Si la autenticación falla, el servidor devuelve un error HTTP (401/403) antes de que se complete la actualización.

Paso 2: STOMP CONNECT

Después de que se establezca la conexión WebSocket, envía un frame STOMP CONNECT para inicializar el protocolo de mensajería:

Frame CONNECT

CONNECT
accept-version:1.2
heart-beat:10000,10000

\x00
HeaderValorDescripción
accept-version1.2Versión del protocolo STOMP
heart-beat10000,10000Intervalo de envío/recepción de heartbeat del cliente en milisegundos

Respuesta esperada: frame CONNECTED

CONNECTED
version:1.2
heart-beat:10000,10000

\x00
El servidor devuelve la versión y los intervalos de heartbeat negociados.

Construcción de frames STOMP

Los frames STOMP siguen un formato simple basado en texto:
COMMAND\n
header1:value1\n
header2:value2\n
\n
body (optional)
\x00
  • Cada frame comienza con un nombre de comando
  • Los headers son pares clave-valor separados por dos puntos, uno por línea
  • Una línea vacía separa los headers del cuerpo
  • El frame termina con un byte nulo (\x00)
def build_stomp_frame(command, headers=None, body=""):
    """Construct a STOMP protocol frame."""
    frame = command + "\n"
    if headers:
        for key, value in headers.items():
            frame += f"{key}:{value}\n"
    frame += "\n"
    frame += body
    frame += "\x00"
    return frame

# Send CONNECT
connect_frame = build_stomp_frame("CONNECT", {
    "accept-version": "1.2",
    "heart-beat": "10000,10000"
})
ws.send(connect_frame)

# Read CONNECTED response
response = ws.recv()

Paso 3: suscribirse a un tópico

Después de establecer la conexión STOMP, suscríbete al tópico de cambios de la organización:

Frame SUBSCRIBE

SUBSCRIBE
id:sub-0
destination:/topic/change/{orgUuid}

\x00
HeaderValorDescripción
idsub-0Identificador de suscripción definido por el cliente
destination/topic/change/{orgUuid}El tópico al que suscribirse
subscribe_frame = build_stomp_frame("SUBSCRIBE", {
    "id": "sub-0",
    "destination": f"/topic/change/{org_uuid}"
})
ws.send(subscribe_frame)
Reemplaza {orgUuid} con el UUID de tu organización. Recupéralo a través del endpoint REST POST /api/org/getOrgV2.

Paso 4: bucle de mensajes y heartbeats

Una vez suscrito, tu aplicación entra en un bucle de mensajes donde:
  1. Recibe frames MESSAGE que contienen datos de eventos
  2. Envía heartbeats para mantener viva la conexión
  3. Recibe heartbeats del servidor

Mecanismo de heartbeat

Los heartbeats son caracteres de salto de línea individuales (\n) enviados al intervalo negociado (10 segundos). Tanto el cliente como el servidor envían heartbeats. Si alguno de los lados deja de recibir heartbeats, la conexión se considera muerta.
import threading
import time

def send_heartbeats(ws, stop_event):
    """Send STOMP heartbeats every 10 seconds."""
    while not stop_event.is_set():
        try:
            ws.send("\n")
        except Exception:
            break
        stop_event.wait(10)

# Start heartbeat sender in background
stop_heartbeat = threading.Event()
heartbeat_thread = threading.Thread(
    target=send_heartbeats,
    args=(ws, stop_heartbeat),
    daemon=True
)
heartbeat_thread.start()

Análisis de frames entrantes

def parse_stomp_frame(raw):
    """Parse a raw STOMP frame into command, headers, and body."""
    # Handle heartbeats (empty frames)
    raw = raw.lstrip("\n\r")
    if not raw or raw == "\x00":
        return None  # Heartbeat

    raw = raw.rstrip("\x00")
    parts = raw.split("\n\n", 1)

    lines = parts[0].split("\n")
    command = lines[0]

    headers = {}
    for line in lines[1:]:
        if ":" in line:
            key, value = line.split(":", 1)
            headers[key] = value

    body = parts[1] if len(parts) > 1 else ""
    return {"command": command, "headers": headers, "body": body}

Bucle de mensajes

try:
    while True:
        raw = ws.recv()
        frame = parse_stomp_frame(raw)

        if frame is None:
            continue  # Heartbeat, skip

        if frame["command"] == "MESSAGE":
            payload = json.loads(frame["body"])
            handle_event(payload)

except KeyboardInterrupt:
    print("Shutting down...")
finally:
    stop_heartbeat.set()
    heartbeat_thread.join()

Paso 5: desconexión adecuada

Antes de cerrar el WebSocket, envía un frame STOMP DISCONNECT:
# Send DISCONNECT frame
disconnect_frame = build_stomp_frame("DISCONNECT")
ws.send(disconnect_frame)

# Close WebSocket
ws.close()
Esto le indica al servidor que el cliente se está desconectando intencionalmente, permitiéndole liberar recursos de inmediato en lugar de esperar un timeout de heartbeat.

Estrategia de reconexión

Las interrupciones de red son inevitables. Implementa una reconexión automática con backoff:
import time

def connect_with_retry(api_token, org_uuid, max_retries=10):
    """Connect to Rhombus WebSocket with exponential backoff."""
    for attempt in range(max_retries):
        try:
            ws = websocket.create_connection(
                "wss://ws.rhombussystems.com:8443/websocket"
                "?x-auth-scheme=api-token",
                header={"x-auth-apikey": api_token},
                timeout=10
            )
            stomp_connect(ws)
            stomp_subscribe(ws, org_uuid)
            print("Connected successfully")
            return ws
        except Exception as e:
            delay = min(5 * (2 ** attempt), 60)
            print(f"Connection failed: {e}. Retrying in {delay}s...")
            time.sleep(delay)

    raise ConnectionError("Max retries exceeded")

Bucle completo de reconexión

while True:
    ws = connect_with_retry(API_TOKEN, ORG_UUID)

    try:
        run_message_loop(ws)
    except websocket.WebSocketConnectionClosedException:
        print("Connection lost. Reconnecting...")
    except KeyboardInterrupt:
        print("User interrupted. Exiting.")
        break
    finally:
        try:
            ws.close()
        except Exception:
            pass

Tiempos de espera de la conexión

TimeoutValorDescripción
Handshake10 segundosTiempo máximo para completar la actualización de WebSocket
Heartbeat10 segundosIntervalo entre pings de keep-alive
Conexión muerta~30 segundosTiempo aproximado antes de que un heartbeat faltante dispare la desconexión

Problemas comunes

SíntomaCausa probableSolución
La conexión se cae cada ~30sNo se están enviando heartbeatsImplementa el thread emisor de heartbeats
El frame CONNECTED nunca se recibeFrame STOMP CONNECT mal formadoVerifica que el formato del frame incluya el terminador nulo \x00
No se reciben mensajesTópico incorrecto o falta el UUID de la organizaciónVerifica que el UUID de la organización coincida con la organización de tu token de API
Bucle de reconexiónToken expirado o revocadoVerifica la validez del token mediante la API REST
Last modified on April 30, 2026