Guía del video: cómo configuramos los GPS SinoTrack ST-901L por mensaje de texto, cómo llegan sus datos a nuestro servidor, cómo se guardan y para qué se usan. Primero el mapa completo, después cada pieza. La IP, los teléfonos y el IMEI que aparecen son de ejemplo. Por Pablo Paniagua, LATAM Code Labs; uso permitido según permisos. Más del sistema en Cómo se construyó ecoplagas.online.

Este video explica cómo conectamos los GPS de nuestros vehículos a nuestro propio sistema. Lo hicimos desde cero: compramos los aparatos, los configuramos con mensajes de texto, montamos un servidor que recibe sus datos y los usamos para reportes y alertas. Primero vamos a ver el bosque, el mapa completo. Después entramos árbol por árbol.


El problema era simple. Queríamos saber dónde está cada vehículo, a qué velocidad va, dónde se detiene y si llegó a tiempo a cada cita. Antes lo medíamos con una aplicación en el teléfono del técnico, pero si el teléfono se apagaba o se quedaba en la central, el recorrido se perdía. Un GPS fijo en el vehículo no depende de nadie.





Este es el mapa completo, son seis cajas. Arriba, los satélites. Le dicen al aparato dónde está, pero no reciben nada. En el vehículo, el aparato GPS calcula su posición y la manda por la red celular 4G, igual que un teléfono. La red la entrega a nuestro servidor, una máquina pequeña en Google Cloud con una dirección fija. El servidor traduce el mensaje y lo guarda en la base de datos, Firestore. Y de ahí la leen las pantallas, los reportes y las alertas de WhatsApp.
Las seis cajas y quién es dueño de cada una:
| Caja | Qué es | De quién |
|---|---|---|
| Satélites | Constelación GPS (y BeiDou) | Gobiernos; gratis, solo se escucha |
| Aparato GPS | SinoTrack ST-901L 4G con un chip de datos | Nuestro, uno por vehículo |
| Red celular | 4G de Kölbi o Claro | Del operador; se paga el plan de datos del chip |
| Receptor | Programa en Node.js en una máquina virtual de Google Cloud | Nuestro |
| Firestore | Base de datos de Firebase | Nuestra, la misma del sistema |
| Pantallas y alertas | GPS 360, reportes, funciones que avisan por WhatsApp | Nuestras |

Una forma fácil de pensarlo es el correo. Cada 20 segundos, el aparato escribe una postal con su posición, la hora y la velocidad. La manda siempre a la misma dirección: una IP y un puerto. Nuestro servidor es el buzón. Abre cada postal, la traduce y la archiva en el expediente del vehículo.


Una duda común es por qué no mandar los datos directo a la base de datos. La razón es que el aparato no habla el idioma de la web. No sabe hacer peticiones HTTP. Abre una conexión TCP, un cable virtual, y escribe líneas de texto en un formato que se llama hache cero dos. Firebase solo entiende HTTP. Por eso hace falta un traductor en el medio, y ese traductor es nuestro receptor.
HTTP es un protocolo de pedido y respuesta: el cliente pide, el servidor responde y se cierra. TCP es el cable de abajo: una conexión que queda abierta y por la que se puede escribir texto libre. El ST-901L abre una conexión TCP y escribe tramas H02; no sabe armar un pedido HTTP ni autenticarse con Firebase. Por eso hay que poner un programa propio que escuche ese puerto TCP.

Árbol uno. La señal.

Alrededor de la Tierra giran unos treinta satélites GPS. Cada uno transmite todo el tiempo su posición y la hora exacta de un reloj atómico. El aparato escucha esas señales y mide cuánto tardó cada una en llegar. Como la señal viaja a la velocidad de la luz, ese tiempo se convierte en distancia.



Con la distancia a un satélite, el aparato sabe que está en algún punto de un círculo enorme. Con dos, en el cruce de los dos. Con tres, el punto queda casi fijo, y con un cuarto satélite corrige su propio reloj. Así obtiene latitud, longitud y hora, con unos cinco metros de error a cielo abierto.
Técnicamente son esferas en tres dimensiones, no círculos. Con tres satélites quedan dos puntos posibles y uno cae fuera de la Tierra; el cuarto satélite hace falta porque el reloj del aparato es barato y no está sincronizado: el cuarto le permite resolver su propio error de reloj como una incógnita más.


Importa algo: el aparato solo recibe. Los satélites no saben que existe. Por eso necesita ver el cielo. Si se instala debajo de una lámina de metal, no calcula nada. Cuando no tiene satélites, el aparato estima su posición por las antenas de celular, con un error de cientos de metros. En la trama eso viene marcado con la letra V, de inválida, y nosotros no la guardamos.


El aparato que usamos es el SinoTrack ST-901L, versión 4G. Por dentro tiene un receptor GPS, un módem celular con su chip, una batería pequeña de respaldo y dos luces de estado. La naranja es la red celular y la azul es el GPS. Se conecta al vehículo con cuatro cables: corriente, tierra, la señal de encendido del motor y un cuarto cable opcional para un relé. La señal de encendido le dice si el vehículo anda o está parqueado.
Instalación: el aparato va escondido pero con vista al cielo (debajo del plástico del tablero, nunca bajo lámina de metal). La señal de encendido (ACC) es la que le permite usar un intervalo con el motor andando y otro con el motor apagado. La batería interna solo cubre cortes cortos.

Para mandar la posición, el aparato usa su chip como cualquier teléfono. Una advertencia para Costa Rica: el 2G ya se apagó. Un aparato viejo de 2G no se va a conectar nunca, haga lo que haga. Hay que comprar la versión 4G.

Árbol dos. Configurarlo por mensaje de texto.


El aparato no tiene pantalla ni botones. Se configura mandándole mensajes de texto al número de su chip, y responde también por mensaje. Antes de empezar se revisan cuatro cosas: que el chip tenga saldo y datos activos, que no tenga PIN, que el aparato sea 4G y que tengamos anotado el IMEI, el número de serie impreso en la etiqueta. La clave de fábrica es cuatro ceros.
Checklist antes de empezar:
0000. En cada comando va pegada al número del comando: en 8030000, 803 es el comando y 0000 la clave.
Primer mensaje: el número de teléfono que va a mandar las órdenes, pegado a la clave, un espacio y un uno. Desde ese momento, el aparato solo obedece a ese número.

Segundo: el APN, que es la puerta de entrada a internet del operador. El comando es ochocientos tres, la clave, un espacio y el APN. Para Kölbi es kolbi4g y para Claro es internet punto ideasclaro. Debe responder SET OK.


Tercero, el paso más importante: a dónde mandar los datos. El comando es 804, la clave, la IP pública de nuestro servidor y el puerto, separados por espacios. La IP es como la dirección del edificio, y el puerto como el número de apartamento. Usamos el puerto 5013, el que se usa por costumbre para este protocolo. Si algo falla, casi siempre es aquí.

Cuarto: cada cuánto reporta. Con ochocientos cinco se fija el intervalo con el motor encendido. Usamos 20 segundos, así el recorrido queda fino en el mapa. Con ochocientos nueve, el intervalo con el motor apagado. Usamos 300 segundos, cinco minutos, porque un vehículo parqueado no necesita reportar seguido.


Quinto: se reinicia con la palabra RESTART, se esperan dos minutos y se manda la palabra erre conf. El aparato contesta con toda su configuración. Ahí se revisan tres cosas: que el modo diga GPRS y no SMS, que la IP y el puerto estén exactos, y que la zona horaria sea E cero cero. El aparato manda la hora universal y el servidor la pasa a hora de Costa Rica.

En total son seis mensajes y unos diez minutos por aparato. Hicimos una guía de una página con cada mensaje listo para copiar y pegar, porque un espacio de más basta para que no funcione.
1. <su número><clave> 1 ejemplo: 888888880000 1
2. 803<clave> <APN> Kölbi: 8030000 kolbi4g · Claro: 8030000 internet.ideasclaro
(si el operador pide usuario y clave: 8030000 APN USUARIO CLAVE)
3. 804<clave> <IP> <puerto> 8040000 203.0.113.25 5013
4. 805<clave> <segundos> 8050000 20 motor encendido
5. 809<clave> <segundos> 8090000 300 motor apagado
6. RESTART (esperar 2 minutos) luego RCONFRespuesta esperada a los pasos 2 a 5: SET OK. En RCONF revisar MODE:GPRS, la IP y el puerto exactos, y TIME ZONE:E00.

Árbol tres. El viaje de un dato.


Sigamos una posición en su viaje. Esto es lo que manda el aparato cada 20 segundos: una línea de texto que empieza con asterisco y termina con numeral. Tiene el IMEI, la hora, la letra A de posición válida, la latitud, la longitud, la velocidad, el rumbo, la fecha y un código de estado.
*HQ,<IMEI>,V1,HHmmss,A,DDMM.MMMM,N,DDDMM.MMMM,W,nudos,rumbo,DDMMAA,estado#
*HQ,861234567890123,V1,143005,A,0956.1234,N,08405.6789,W,027.50,180,290826,FFFFFBFF#Nuestro aparato manda la variante V6, que trae los mismos campos y agrega datos de la celda y del chip al final. Por eso el traductor no reconoce la trama por su nombre sino por su forma: si el campo 3 es una hora de seis dígitos y el campo 4 es A, V o B, es una posición. Los latidos son *HQ,<IMEI>,XT# (o HTBT con la batería).



Hay tres trampas en esa línea. La latitud no viene en grados decimales sino en grados y minutos, y hay que convertirla. La velocidad viene en nudos, y se multiplica por uno coma ocho cinco dos para pasarla a kilómetros por hora. Y la hora viene en tiempo universal. Si el reloj del aparato está adelantado, o atrasado más de un día y medio, el servidor usa la hora de llegada y marca la posición como de hora dudosa.
FUNCIÓN aGrados(valor, hemisferio): // "0956.1234", "N"
punto = posición del "."
grados = valor[0 .. punto-2] // "09"
minutos= valor[punto-2 ..] // "56.1234"
g = grados + minutos / 60
si hemisferio es S o W: g = -g
devolver g
velocidadKmh = nudos * 1.852
FUNCIÓN elegirHora(horaTrama, ahora):
si no hay hora en la trama -> ahora, dudosa
si horaTrama > ahora + 5 min -> ahora, dudosa (reloj adelantado)
si horaTrama < ahora - 36 h -> ahora, dudosa (reloj atrasado)
si no -> horaTrama, confiable
La conexión queda abierta todo el tiempo. Cuando no hay posición nueva, el aparato manda un latido corto para decir que sigue vivo, y el servidor le responde. Si una conexión pasa 15 minutos sin decir nada, el servidor la cierra y el aparato abre otra. Y si el aparato se queda sin señal, guarda las posiciones y las reenvía después.
al llegar datos por el socket:
buffer += datos
mientras haya una trama completa (de "*" a "#") en el buffer:
sacarla e interpretarla
si es latido: responder "*HQ,<IMEI>,XT#" (eco)
si es posición: entregarla al acumulador
// si llegan más de 4 KB sin "#", es basura: se vacía el buffer
socket con 15 minutos sin datos: se cierra
keep-alive de TCP cada 60 s
Árbol cuatro. El receptor.




El receptor es un programa de unas 600 líneas en Node.js. Corre en la máquina más pequeña de Google Cloud, una e2-micro con Ubuntu, en el mismo proyecto que Firebase. Tiene tres piezas de infraestructura. Una IP pública reservada, que no cambia aunque la máquina se reinicie. Una regla de firewall que abre el puerto cinco mil trece. Y un servicio de systemd que lo arranca solo y lo levanta si se cae.
# /etc/systemd/system/ecoplagas-gps.service (resumen)
[Service]
User=ecogps # no corre como root
WorkingDirectory=/opt/ecoplagas-gps
ExecStart=/usr/bin/node servidor.js
Restart=always
RestartSec=5
Environment=PUERTO=5013
Environment=FLUSH_SEG=120
Environment=METROS_MINIMOS=25
Environment=QUIETO_SEG=300En Google Cloud: crear la VM (e2-micro), reservar una IP externa estática y asignársela, crear una regla de firewall que permita TCP 5013 desde cualquier origen, y darle a la cuenta de servicio de la VM el rol roles/datastore.user. Comandos útiles en la máquina: systemctl status ecoplagas-gps y journalctl -u ecoplagas-gps -f.

Y no guarda ninguna contraseña. La máquina tiene su propia identidad en Google Cloud, y a esa identidad se le dio permiso para escribir en Firestore. Si alguien copia el código, no se lleva ninguna llave. Desde el navegador, esas colecciones son de solo lectura: nadie puede inventar la posición de un vehículo.


Un aparato que reporta cada 20 segundos manda unas 4.300 posiciones al día. Escribir cada una sería caro y lento. Por eso el receptor las junta en memoria y las escribe cada dos minutos, todas juntas. Además descarta las posiciones a menos de 25 metros de la anterior, salvo que hayan pasado cinco minutos. Así, un vehículo en ruta pasa de 4.300 escrituras a unas 700 por día.
FUNCIÓN agregar(posición):
si posición no es válida (letra V): descartar
si está a menos de 25 m de la anterior
y no han pasado 5 minutos: descartar
cola[imei].push({lat, lng, vel, rum, bat, ts})
si la cola llega a 50 puntos: vaciar(imei)
CADA 120 SEGUNDOS: vaciar(todos)
FUNCIÓN vaciar(imei):
puntos = sacar todo de la cola
agrupar por documento destino (clave_fecha_bBloque)
por cada grupo: set(merge) con arrayUnion(puntos) e increment(totalPuntos)
actualizar flota_gps_estado/{imei} con la última posición
si Firestore falla: los puntos vuelven a la cola


Todo se guarda en tres colecciones. La primera es el rastro: un documento por vehículo, por día y por bloque de seis horas, con la lista de puntos. Cada punto lleva latitud, longitud, velocidad, rumbo, batería y hora. La segunda es la última posición de cada aparato, un documento pequeño para el mapa en vivo. La tercera dice a qué vehículo pertenece cada IMEI. Si llega un aparato desconocido, se da de alta solo, para no perder sus datos.
flota_tracks/{clave}_{AAAA-MM-DD}_b{0..3} bloque = hora local / 6
usuario: "apv-01" vehiculo: "APV 01" imei fuente: "gps"
fecha, bloque, totalPuntos, actualizado
puntos: [ { lat, lng, prec: null, vel, rum, bat, car: null, ts }, ... ]
flota_gps_estado/{imei}
clave, vehiculo, lat, lng, vel, rum, estadoHex, horaGps, horaConfiable, actualizado
flota_gps_equipos/{imei}
imei, clave, vehiculo, asignado, activo, visto, creadoEl formato de flota_tracks es el mismo que ya usaba la aplicación de rastreo del teléfono, así que las pantallas que leían ese rastro no hubo que tocarlas. El campo fuente distingue un origen del otro. Desde el navegador, las colecciones del GPS son de solo lectura; el receptor escribe con permisos de administrador.

Una lección que aprendimos: el receptor recordaba para siempre a qué vehículo pertenecía cada aparato. Cuando asignábamos un aparato nuevo, seguía guardando con su número de serie hasta reiniciar el servicio. Ahora esa memoria vence cada cinco minutos, y la asignación surte efecto sola.

Antes de tener el aparato en la mano, probamos todo con un simulador que imita un ST-901L recorriendo San José, y con 31 pruebas automáticas del traductor. También hay un modo que solo imprime lo que llega, sin escribir en la base de datos.

Árbol cinco. Para qué sirve.


Con los puntos guardados, el sistema arma el día de cada vehículo. Una parada es estar cinco minutos dentro de un radio de 60 metros. Si la parada queda a menos de 500 metros de una cita, es esa cita. Si es en la central, es la central. Así el día se ve como una línea de tiempo: central, cita, tramo, cita, regreso.
parada = 5 minutos o más dentro de un radio de 60 m
juntar paradas a menos de 150 m; descartar tramos de menos de 200 m
clasificar:
a <= 500 m de una cita de su ruta -> cita
a <= 500 m de la central -> central
a <= 100 m de una gasolinera y <= 25 min -> gasolinera
otra -> sin cita

Con cada lote nuevo de puntos, una función revisa la velocidad. Si el vehículo pasa de 90 kilómetros por hora por más de 20 segundos, o toca 100, sale un aviso por WhatsApp con el mapa del tramo. Cada cinco minutos, otra función revisa las rutas que terminaron y avisa de paradas largas lejos de toda cita. Para no acusar a nadie por echar gasolina, cargamos 334 gasolineras de OpenStreetMap.
ALERTA DE VELOCIDAD (con cada lote nuevo de puntos):
racha = puntos sobre 90 km/h confirmados por distancia/tiempo (2 o más)
unir rachas a menos de 10 minutos
alertar si la racha dura 20 s o más sobre 90, o si el pico llega a 100
PARADAS FUERA DE RUTA (cada 5 minutos):
ruta cerrada = el vehículo volvió a la central 15 min después de su última cita
avisar paradas de 20 min o más, a más de 500 m de toda cita,
fuera de la central y que no sean gasolinera ni cita movida o cancelada
Cada dos horas se resume la velocidad de cada empleado, filtrando los saltos falsos del aparato. En el caso que investigamos, después de las alertas el tiempo por encima de 90 bajó de cuatro por ciento a menos de medio por ciento.

Y si un aparato no conecta.

Se revisa en este orden. Luz naranja parpadeando: no tiene red. Revise saldo, PIN, o que sea 4G. Luz azul parpadeando: no ve satélites, necesita cielo. No contesta RCONF: no está recibiendo mensajes. Contesta pero no llegan datos: el APN, la IP o el puerto están mal, o el firewall está cerrado. Y si llegan datos pero no se guardan, el problema es el permiso de la máquina.
Prueba de puerto desde cualquier computadora: nc -vz <IP> 5013 tiene que decir que conectó. Si el log del receptor muestra IMEI ... sin asignar, el aparato está bien: falta ponerle vehiculo y clave en flota_gps_equipos/<imei>.

Volvamos al bosque. Satélites que dicen dónde está. Un aparato que lo manda por la red celular. Un receptor que lo traduce. Una base de datos que lo guarda. Y pantallas y alertas que lo usan. Con un aparato de bajo costo, una máquina pequeña y unas 600 líneas de código, tenemos el recorrido de toda la flota dentro de nuestro propio sistema.
EcoPlagas · 31 de octubre de 2026. Código del receptor: unas 600 líneas en Node.js (servidor, traductor H02 y acumulador), con simulador y 31 pruebas automáticas.