mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 07:22:53 +00:00
Merge pull request #581 from Tria-plc/freight/feature/first_mile_invoice
Freight/feature/first mile invoice
This commit is contained in:
150
apps/edr-freight-api/src/modules/gps-tracking/README.md
Normal file
150
apps/edr-freight-api/src/modules/gps-tracking/README.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# GPS Tracking (GT06) — Operations & Device Configuration
|
||||
|
||||
GT06 trackers speak a **raw TCP binary protocol**, not HTTP/HTTPS. This shapes
|
||||
everything about how the service is deployed and how devices are pointed at it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why GPS needs its own dedicated TCP port
|
||||
|
||||
- **Not HTTP.** GT06 devices send binary frames
|
||||
(`0x78 0x78 | len | protocol | payload | serial | CRC16 | 0x0D 0x0A`).
|
||||
An HTTP server receiving these answers `400 Bad Request` and closes.
|
||||
- **Dedicated port required.** A listening socket is keyed on `(IP, port)`; two
|
||||
listeners on the same pair collide (`EADDRINUSE`). The REST API already owns
|
||||
its port, so GPS traffic needs a separate one.
|
||||
- **No hostname routing.** GT06 frames carry no `Host` header and no TLS SNI, so
|
||||
L7 proxies (Nginx `http`, AWS ALB, Cloudflare proxy) cannot route them by
|
||||
domain. Routing must happen at **Layer 4 (TCP)** by port.
|
||||
- **DNS carries no port.** An A record maps a name to an IP only. The tracker
|
||||
config must state the port explicitly (e.g. `gps.example.com:5023`).
|
||||
|
||||
### Operational requirements
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Protocol | Raw TCP (not HTTP, not TLS) |
|
||||
| Default port | `5023` (configurable via `GT06_TCP_PORT`) |
|
||||
| Listener bind | `0.0.0.0` inside the `freight-gps` container |
|
||||
| Edge terminator | **L4** — AWS NLB or Nginx `stream {}`. **Not** ALB / Cloudflare proxy. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Port configuration
|
||||
|
||||
`5023` is only this project's default — **not** a GT06 protocol requirement. The
|
||||
listener binds whatever `GT06_TCP_PORT` says, as long as trackers are configured
|
||||
with the same number.
|
||||
|
||||
Host and container ports are decoupled in `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
freight-gps:
|
||||
ports:
|
||||
- "${GT06_TCP_PORT:-5023}:5023" # host is configurable; container fixed
|
||||
environment:
|
||||
GT06_TCP_PORT: "5023" # pinned inside the container
|
||||
```
|
||||
|
||||
- The **container** always listens on `5023`.
|
||||
- The **host/public** port is configurable (443, 5023, 9000, …) via the root
|
||||
`.env`'s `GT06_TCP_PORT`.
|
||||
- This split is required because the image runs as a **non-root** user
|
||||
(`nestjs`, uid 1001), which cannot bind ports `<1024`. Docker (root) binds the
|
||||
host port and forwards to `5023` inside.
|
||||
- Running **outside Docker** (`pnpm dev:gps`, systemd), `GT06_TCP_PORT` is the
|
||||
actual bind port, so `<1024` needs root or `CAP_NET_BIND_SERVICE`.
|
||||
- **443 is allowed but risky:** GT06 stays raw TCP, not TLS. Middleboxes that
|
||||
expect a TLS handshake on 443 may drop the connection.
|
||||
|
||||
---
|
||||
|
||||
## 3. Deployment topology
|
||||
|
||||
The GT06 listener runs as its own process (`dist/main.gps.js`, module
|
||||
`GpsIngestModule`) — DB + GPS only, no HTTP server. It shares the `edr_freight`
|
||||
DB with the API; the DB is the seam (ingester writes `gps_devices` /
|
||||
`gps_positions`, API reads them).
|
||||
|
||||
```
|
||||
freight-api HTTP :3001 GT06_TCP_PORT=0 (listener off, applies migrations)
|
||||
freight-gps TCP :5023 DB_MIGRATIONS_RUN=false (owns the tracker socket)
|
||||
```
|
||||
|
||||
`DB_MIGRATIONS_RUN=false` keeps the second process from racing migrations.
|
||||
|
||||
Horizontal scale: each tracker holds one long-lived TCP connection with
|
||||
per-socket session state, so N `freight-gps` replicas can run behind an L4 LB —
|
||||
each device sticks to one replica. `ensureDevice` is safe under concurrency
|
||||
(unique IMEI).
|
||||
|
||||
---
|
||||
|
||||
## 4. Device configuration (GT06 side)
|
||||
|
||||
Config is done by **SMS to the tracker's SIM**. Commands below are the canonical
|
||||
Concox/GT06 set — **verify against your unit's sheet**, syntax varies by firmware.
|
||||
Default command password is usually `123456`.
|
||||
|
||||
Prep: data-enabled SIM, SMS on, **SIM PIN off**, know your carrier APN.
|
||||
|
||||
```
|
||||
STATUS# # 1. sanity check — returns GSM/GPS/batt/GPRS
|
||||
APN,<apn># # 2. carrier data APN (add ,user,pass if needed)
|
||||
SERVER,1,gps.example.com,5023,0# # 3. point at server (1=domain). Port MUST match GT06_TCP_PORT
|
||||
GPRSON,1# # 4. enable data
|
||||
GPSON,1# # enable GPS
|
||||
TIMER,10# # 5. upload interval, seconds (some use UPLOAD,10#)
|
||||
RESET# # 6. reboot so it reconnects (many cache DNS until reboot)
|
||||
```
|
||||
|
||||
Raw-IP variant of step 3: `SERVER,0,203.0.113.50,5023,0#`
|
||||
Custom host port (e.g. 443): `SERVER,1,gps.example.com,443,0#`
|
||||
|
||||
### Verify from the server
|
||||
|
||||
```bash
|
||||
docker compose logs -f freight-gps | grep -Ei "login|Auto-registering|ingester up"
|
||||
nc -vz gps.example.com 5023
|
||||
curl -H "Authorization: Bearer <token>" https://api.example.com/api/gps/positions/latest
|
||||
```
|
||||
|
||||
First login packet **auto-registers** the IMEI (no manual step). `online:true`
|
||||
only when `lastSeenAt` < 5 min (computed at read time).
|
||||
|
||||
### Link a tracker to a vehicle (optional)
|
||||
|
||||
Auto-register leaves `vehicleId` null. Attach it (needs `tracking.manage`):
|
||||
|
||||
```
|
||||
PATCH /api/gps/devices/:id { "vehicleId": "<uuid>", "name": "Truck 03-ET" }
|
||||
```
|
||||
|
||||
### Failure map
|
||||
|
||||
| Symptom | Cause |
|
||||
| --- | --- |
|
||||
| No SMS reply | SIM PIN on / no signal / wrong number |
|
||||
| Replies but never connects | APN wrong, or `SERVER` port ≠ `GT06_TCP_PORT` |
|
||||
| Connects then drops | server not ACKing, or middlebox on 443 expecting TLS |
|
||||
| Registered but `online:false` | packets blocked by firewall — open inbound TCP |
|
||||
| Wrong location / `positioned:false` | no GPS fix yet — open sky, cold start ~1–2 min |
|
||||
|
||||
---
|
||||
|
||||
## 5. Security
|
||||
|
||||
- GT06 authenticates with **IMEI only**, which is **spoofable**. Anyone who can
|
||||
reach the port can inject fake positions.
|
||||
- **Do not** expose the port to `0.0.0.0/0`. Restrict at the firewall / security
|
||||
group to the SIM provider's **APN / IP range**.
|
||||
- Trackers must use the same host+port as the server:
|
||||
`SERVER,1,gps.example.com,<port>,0#`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Edge (L4) termination
|
||||
|
||||
See [`infrastructure/nginx/gps-stream.conf`](../../../../../infrastructure/nginx/gps-stream.conf)
|
||||
for an Nginx `stream {}` example, and the AWS NLB notes in the same file.
|
||||
Reminder: **L4 only** — an HTTP proxy cannot route GT06.
|
||||
Reference in New Issue
Block a user