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
| Header | Valor | Descripción |
|---|
accept-version | 1.2 | Versión del protocolo STOMP |
heart-beat | 10000,10000 | Intervalo 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
| Header | Valor | Descripción |
|---|
id | sub-0 | Identificador 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:
- Recibe frames MESSAGE que contienen datos de eventos
- Envía heartbeats para mantener viva la conexión
- 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
| Timeout | Valor | Descripción |
|---|
| Handshake | 10 segundos | Tiempo máximo para completar la actualización de WebSocket |
| Heartbeat | 10 segundos | Intervalo entre pings de keep-alive |
| Conexión muerta | ~30 segundos | Tiempo aproximado antes de que un heartbeat faltante dispare la desconexión |
Problemas comunes
| Síntoma | Causa probable | Solución |
|---|
| La conexión se cae cada ~30s | No se están enviando heartbeats | Implementa el thread emisor de heartbeats |
El frame CONNECTED nunca se recibe | Frame STOMP CONNECT mal formado | Verifica que el formato del frame incluya el terminador nulo \x00 |
| No se reciben mensajes | Tópico incorrecto o falta el UUID de la organización | Verifica que el UUID de la organización coincida con la organización de tu token de API |
| Bucle de reconexión | Token expirado o revocado | Verifica la validez del token mediante la API REST |