Diseñar e implementar un protocolo de comunicación eficiente para dispositivos GPS que permita transmitir datos de localización y estado en tiempo real a un servidor central, considerando:
- Restricciones de batería en dispositivos móviles
- Ancho de banda limitado (redes 2G/3G/4G)
- Transmisión confiable sobre redes no confiables
- Seguridad básica de los datos
| Capa | Conceptos Implementados | Ubicación en el Código |
|---|---|---|
| Capa de Enlace | CRC-16, detección de errores | calcular_checksum() |
| Capa de Red | Direccionamiento IP, enrutamiento | Sockets UDP |
| Capa de Transporte | UDP, ARQ, control de flujo | ACK, reintentos, timeouts |
| Capa de Aplicaciones | Cliente-servidor, sockets | Arquitectura completa |
┌─────────────────┐ ┌─────────────────┐
│ Dispositivo GPS │ │ Servidor Central│
│ (Cliente) │ │ (Receptor) │
├─────────────────┤ ├─────────────────┤
│ • Genera datos │ ───UDP (30B)────> │ • Recibe datos │
│ • Empaqueta │ │ • Valida CRC │
│ • Envía cada 5s │ <───ACK (10B)─── │ • Almacena log │
│ • Espera ACK │ │ • Registra stats│
└─────────────────┘ └─────────────────┘
┌──────────────────────────────────────────────────────────┐
│ CABECERA (10 bytes) │
├────┬────┬──────┬──────┬──────────┬────────┬──────────────┤
│VER │TIPO│ ID │ SEQ │CHECKSUM │ FLAGS │ (reserva) │
│ 1B │ 1B │ 2B │ 2B │ 2B │ 2B │ │
└────┴────┴──────┴──────┴──────────┴────────┴──────────────┘
┌──────────────────────────────────────────────────────────┐
│ PAYLOAD (20 bytes) │
├────────┬─────────┬─────┬──────────┬─────┬───────┬───┬────┤
│LATITUD │LONGITUD │ ALT │TIMESTAMP │ VEL │RUMBO │BAT│EST │
│ 4B │ 4B │ 2B │ 4B │ 2B │ 2B │1B │ 1B │
└────────┴─────────┴─────┴──────────┴─────┴───────┴───┴────┘
| Campo | Tamaño | Descripción | Ejemplo |
|---|---|---|---|
| VER | 1 byte | Versión del protocolo | 0x01 |
| TIPO | 1 byte | Tipo de mensaje (GPS/ACK/HEARTBEAT) | 0x01 = GPS |
| ID_DISP | 2 bytes | Identificador único del dispositivo | 1234 |
| SEQ | 2 bytes | Número de secuencia (0-65535) | 42 |
| CHECKSUM | 2 bytes | CRC-16 para detección de errores | 0x3FF5 |
| FLAGS | 2 bytes | Estado del dispositivo (ver tabla abajo) | 0x0C |
| LATITUD | 4 bytes | Grados × 10⁷ (permite ±180°) | -173935000 = -17.3935° |
| LONGITUD | 4 bytes | Grados × 10⁷ (permite ±180°) | -661570000 = -66.1570° |
| ALTITUD | 2 bytes | Metros sobre el nivel del mar | 2558 m |
| TIMESTAMP | 4 bytes | Unix timestamp (segundos desde 1970) | 1738267815 |
| VELOCIDAD | 2 bytes | km/h × 10 | 450 = 45.0 km/h |
| RUMBO | 2 bytes | Grados × 10 (0-3600) | 1350 = 135.0° |
| BATERÍA | 1 byte | Porcentaje (0-100) | 85 % |
| ESTADO | 1 byte | Byte adicional de estado | 0x00 |
| Bit | Nombre | Descripción |
|---|---|---|
0x01 |
BATERÍA_BAJA | Batería < 20% |
0x02 |
SOS | Señal de emergencia |
0x04 |
EN_MOVIMIENTO | Velocidad > 5 km/h |
0x08 |
IGNICIÓN_ON | Motor encendido |
# Nuestro protocolo binario
mensaje_binario = 30 bytes
# Alternativa JSON (mismo contenido)
mensaje_json = {
"id": 1234,
"seq": 42,
"lat": -17.3935,
"lon": -66.1570,
"alt": 2558,
"vel": 45.0,
# ...
}
# Tamaño: ~180 bytes
# AHORRO: 83% de ancho de bandaEnvío cada 5 segundos:
• Mensajes/día: 17,280
• Datos/día: 30 bytes × 17,280 = ~500 KB
• Consumo red 3G: ~0.2% batería/día
Si usáramos JSON:
• Datos/día: 180 bytes × 17,280 = ~3 MB
• Consumo red 3G: ~1.2% batería/día
| Característica | UDP | TCP | Decisión |
|---|---|---|---|
| Overhead | 8 bytes | 20+ bytes | ✅ UDP |
| Handshake | No requiere | 3-way handshake | ✅ UDP (ahorra batería) |
| Latencia | Baja | Media/Alta | ✅ UDP (tiempo real) |
| Confiabilidad | No garantizada | Garantizada | |
| Orden | No garantizado | Garantizado |
Decisión: Usar UDP + mecanismos propios de confiabilidad
"UDP no asegura la integridad de los datos ni implementa control de flujo... Es extremadamente simple, no necesita almacenar información acerca del intercambio en curso"
"El streaming es tolerante a pérdidas pero requiere retardos acotados"
Aplicación: GPS tolera perder 1-2 posiciones, pero necesita latencia baja.
def calcular_checksum(datos):
"""
Implementación de CRC-16-ANSI
Polinomio: 0xA001
"""
crc = 0xFFFF
for byte in datos:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc & 0xFFFFCapacidad de Detección:
- ✅ 100% de errores de 1 bit
- ✅ 100% de errores de 2 bits
- ✅ 99.998% de errores de burst ≤ 16 bits
Fundamento Teórico:
"CRC detecta errores en burst de una longitud menor o igual al grado del polinomio generador"
# En el servidor
if seq <= ultima_seq:
# Mensaje duplicado
self.mensajes_duplicados += 1
if seq > ultima_seq + 1:
# Mensajes perdidos
perdidos = seq - ultima_seq - 1
self.mensajes_perdidos += perdidosDetecta:
- 🔄 Mensajes duplicados
- 📉 Mensajes perdidos
⚠️ Desorden en la recepción
Fundamento Teórico:
"Con sólo dos números de paquete es suficiente... El receptor debe verificar que los paquetes recibidos tengan el número de secuencia esperado"
# Cliente envía datos
mensaje = empaquetar_mensaje_gps(...)
socket.sendto(mensaje, servidor)
# Espera ACK con timeout
socket.settimeout(3.0)
try:
ack, _ = socket.recvfrom(1024)
# ACK recibido ✓
except socket.timeout:
# Timeout, reintentarFundamento Teórico:
"Al recibir un ACK debe marcar ese paquete como recibido"
MAX_REINTENTOS = 3
TIMEOUT_BASE = 3.0
for intento in range(MAX_REINTENTOS):
timeout = TIMEOUT_BASE * (2 ** intento) # Backoff exponencial
socket.settimeout(timeout)
try:
enviar_mensaje()
ack = esperar_ack()
break # Éxito
except socket.timeout:
if intento == MAX_REINTENTOS - 1:
print("❌ Mensaje perdido definitivamente")Fundamento Teórico:
"La fase de espera exponencial... ajusta las retransmisiones de manera que estimen la carga actual"
# 1. Autenticación por ID de Dispositivo
dispositivos_autorizados = {1234, 5678, 9012}
if id_dispositivo not in dispositivos_autorizados:
print("⛔ Dispositivo no autorizado")
return
# 2. Validación de Timestamp
timestamp_actual = time.time()
diferencia = abs(timestamp_mensaje - timestamp_actual)
if diferencia > 300: # 5 minutos de tolerancia
print("⚠️ Timestamp sospechoso (posible replay attack)")
# 3. Verificación de Checksum
if not verificar_checksum(mensaje):
print("❌ Mensaje corrupto o manipulado")
returnGPS/
│
├── src/
│ ├── gps_protocolo.py # Librería compartida
│ ├── gps_cliente.py # Simulador de dispositivo GPS
│ └── gps_servidor.py # Servidor central
└── README.md
- Python 3.9+ (recomendado)
# 1) Probar el protocolo
python gps_protocolo.py
# 2) Iniciar servidor
python src/gps_servidor.py
# 3) Ejecutar cliente
python src/gps_cliente.py# Ejecutar tests automáticos
python src/gps_protocolo.pySalida esperada:
=== Prueba del Protocolo GPS ===
1. Creando mensaje GPS...
Tamaño del mensaje: 30 bytes
✓ Mensaje válido
2. Desempaquetando mensaje...
✓ Checksum correcto
✓ Datos íntegros
3. Probando mensaje corrupto...
✓ Error detectado: Checksum inválido
4. Creando mensaje ACK...
✓ ACK válido (10 bytes)
# Opción 1: Configuración por defecto
python src/gps_servidor.py
# Opción 2: Puerto personalizado + ACK deshabilitado
python src/gps_servidor.py 8888 false
# Opción 3: Puerto + ACK + log path + max log (KB) + ventana (seg)
python src/gps_servidor.py 8888 true logs/gps_log.txt 1024 300
# Opción 4: Deshabilitar auto-cliente
python src/gps_servidor.py 9999 true gps_log.txt 1000 300 falseLa interfaz ahora es nativa en Python y controla el servidor/cliente directamente.
# Instalar UI
pip install PyQt5
# Iniciar servidor (puerto por defecto 9999)
python src/gps_servidor.py
# Iniciar servidor en otro puerto (ej: 8888)
python src/gps_servidor.py 8888
# Abrir UI
python src/gps_ui.pySi el puerto 9999 está ocupado:
netstat -ano | findstr :9999
taskkill /PID <PID> /FPantalla del servidor:
============================================================
SERVIDOR GPS CENTRAL
============================================================
Puerto: 9999
ACK automático: Sí
============================================================
[✓] Servidor escuchando en puerto 9999
[✓] Esperando dispositivos GPS...
# Modo interactivo (recomendado)
python gps_cliente.pyMenú interactivo:
┌────────────────────────────────────────────┐
│ SIMULADOR DE DISPOSITIVO GPS │
└────────────────────────────────────────────┘
Seleccione el modo de operación:
1. 🅿️ Vehículo estacionado
2. 🏙️ Movimiento urbano (30 km/h)
3. 🛣️ Carretera (80 km/h)
4. ⚙️ Configuración personalizada
5. 💓 Enviar solo HEARTBEAT
6. ❌ Salir
Opción: _
Causa: El puerto 9999 ya está siendo usado por otro programa.
Soluciones:
- Esperar 30-60 segundos y reintentar
- Usar otro puerto:
python src/gps_servidor.py 8888
python src/gps_cliente.py 127.0.0.1 8888 1234- Liberar el puerto (Windows):
netstat -ano | findstr :9999
taskkill /PID <número> /FEn el cliente:
[→] Mensaje #1 enviado (30 bytes)
Pos: -17.393500°, -66.157000°
Vel: 0.0 km/h, Bat: 100%
[←] ACK recibido para mensaje #1
[→] Mensaje #2 enviado (30 bytes)
Pos: -17.393520°, -66.156980°
Vel: 32.5 km/h, Bat: 99%
[←] ACK recibido para mensaje #2
En el servidor:
────────────────────────────────────────────────────────────
[←] DATOS GPS RECIBIDOS
────────────────────────────────────────────────────────────
Origen: 127.0.0.1:54321
Dispositivo: GPS #1234
Secuencia: #2
Coordenadas: -17.3935200°, -66.1569800°
Velocidad: 32.5 km/h
Rumbo: 45.0°
Batería: 99%
Estado: EN MOVIMIENTO, 🔑IGNICIÓN ON
────────────────────────────────────────────────────────────
[→] ACK enviado a GPS #1234 (SEQ=2)
- Abrir Wireshark
- Seleccionar interfaz:
Loopback (lo0) - Filtro de captura:
udp.port == 9999 - Iniciar captura
- Ejecutar cliente y servidor
Frame 1: 58 bytes on wire
Ethernet II
Internet Protocol Version 4
Src: 127.0.0.1
Dst: 127.0.0.1
User Datagram Protocol
Src Port: 54321
Dst Port: 9999
Length: 38 (8 UDP + 30 datos)
Data (30 bytes):
01 01 04 d2 00 01 3f f5 00 0c f5 15 7e 50 fc 0e
74 7a 09 fe 67 fd b3 08 01 c2 05 46 55 00
Desglose hexadecimal:
| Bytes | Campo | Valor Hex | Valor Decimal | Significado |
|---|---|---|---|---|
01 |
VER | 0x01 | 1 | Versión 1 |
01 |
TIPO | 0x01 | 1 | DATOS_GPS |
04 d2 |
ID_DISP | 0x04D2 | 1234 | GPS #1234 |
00 01 |
SEQ | 0x0001 | 1 | Mensaje #1 |
3f f5 |
CHECKSUM | 0x3FF5 | 16373 | CRC-16 |
00 0c |
FLAGS | 0x000C | 12 | MOV + IGN |
f5 15 7e 50 |
LAT | -173935000 | -17.3935° | Latitud |
fc 0e 74 7a |
LON | -661570000 | -66.1570° | Longitud |
09 fe |
ALT | 0x09FE | 2558 | 2558 m |
67 fd b3 08 |
TIME | 0x67FDB308 | 1744511752 | 2025-04-10 |
01 c2 |
VEL | 0x01C2 | 450 | 45.0 km/h |
05 46 |
RUMBO | 0x0546 | 1350 | 135.0° |
55 |
BAT | 0x55 | 85 | 85% |
00 |
EST | 0x00 | 0 | Normal |
Data (10 bytes):
01 02 04 d2 00 01 a7 3c 00 00
| Bytes | Campo | Significado |
|---|---|---|
01 |
VER | Versión 1 |
02 |
TIPO | ACK |
04 d2 |
ID_DISP | GPS #1234 |
00 01 |
SEQ_ACK | Confirmando mensaje #1 |
a7 3c |
CHECKSUM | CRC-16 del ACK |
00 00 |
FLAGS | Sin flags |
============================================================
ESTADÍSTICAS DEL SERVIDOR
============================================================
Mensajes recibidos: 100
Mensajes perdidos: 2 (2%)
Mensajes duplicados: 0
Errores detectados: 1 (checksum inválido)
Dispositivos activos: 1
============================================================
DISPOSITIVOS CONECTADOS:
----------------------------------------------------------
GPS #1234 | Mensajes: 100 | Última SEQ: 102 | Hace: 5s
| Pos: -17.395123°, -66.152456° |
| Vel: 45.2 km/h | Bat: 82%
----------------------------------------------------------
# Simulación con pérdida de paquetes del 5%
Total enviados: 100 mensajes
Total recibidos: 95 mensajes
Pérdidas: 5 mensajes (5%)
Duplicados: 0 mensajes
Errores CRC: 0 mensajes
# Todos los mensajes perdidos fueron detectados por secuencia
# No hubo datos corruptos no detectados (CRC 100% efectivo)Concepto: Detección de errores con CRC
def calcular_checksum(datos):
crc = 0xFFFF
for byte in datos:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc & 0xFFFFConcepto: Fragmentación y MTU
# Nuestro mensaje (30 bytes) nunca necesita fragmentarse
# MTU típico Ethernet: 1500 bytes
# MTU típico 3G/4G: 1280-1500 bytes
# Nuestro protocolo: 30 bytes << MTUConcepto: UDP + ARQ personalizado
socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Implementamos nuestro propio:
# - Numeración de secuencia
# - ACKs
# - Timeouts
# - ReintentosConcepto: Arquitectura cliente-servidor
# Cliente:
# - Inicia solicitudes
# - Gestiona interfaz de usuario
#
# Servidor:
# - Siempre disponible
# - Procesa solicitudes
# - Mantiene estado| Métrica | Valor | Comparación |
|---|---|---|
| Tamaño mensaje | 30 bytes | JSON: ~180 bytes (-83%) |
| Overhead UDP | 8 bytes | TCP: 20+ bytes (-60%) |
| Handshake | 0 ms | TCP: 3-way (50-150ms) |
| Consumo batería | ~0.2%/día | JSON: ~1.2%/día (-83%) |
| Mecanismo | Efectividad |
|---|---|
| CRC-16 | 99.998% detección errores |
| Secuencia | 100% detección duplicados/pérdidas |
| ACK | Confirmación explícita |
| Reintentos | 3 intentos = 99.9% entrega |
# Soporta hasta:
- 65,535 dispositivos únicos (ID de 2 bytes)
- 65,535 mensajes por sesión (SEQ de 2 bytes)
- Múltiples servidores (arquitectura distribuible)- ✅ Protocolo binario eficiente (30 bytes)
- ✅ Transmisión UDP optimizada para tiempo real
- ✅ Manejo robusto de errores (4 capas)
- ✅ Seguridad básica implementada
- ✅ Aplicaciones funcionales cliente-servidor
| Concepto | Implementación |
|---|---|
| CRC, checksums | calcular_checksum() |
| IP, MTU, fragmentación | Mensaje < MTU |
| UDP, ARQ, timeouts | Sockets + reintentos |
| Cliente-servidor, sockets | Arquitectura completa |
| Protocolo | Tamaño | Uso | Ventaja |
|---|---|---|---|
| Nuestro GPS | 30 bytes | Rastreo vehicular | Eficiencia |
| NMEA 0183 | ~80 bytes | GPS náutico/aéreo | Texto legible |
| GPX | ~200 bytes | Rutas/waypoints | Estándar XML |
| UBX (u-blox) | Variable | GPS profesional | Precisión |
Este proyecto está bajo la licencia MIT. Ver LICENSE.