A GPS/GSM tracking unit looks simple from the outside — a box wired into a vehicle's ignition that somehow makes a dot move on a map — but there are four distinct systems working together: the GPS receiver, the cellular modem, the backend that ingests pings, and the map client that renders them. Here is how those pieces actually fit together.
Acquiring a Fix on the GPS Side
The GPS module in the tracker listens to signals from GPS (and often GLONASS/Galileo, on multi-constellation chipsets) satellites and computes latitude, longitude, speed over ground, and heading, typically emitted as NMEA sentences over a serial UART. Getting an initial fix can take anywhere from a couple of seconds with cached almanac data to a minute or more from a cold start, which is why trackers are usually kept powered continuously rather than switched off between trips.
Most modules also report a fix-quality value (HDOP) alongside the coordinates, and it is worth surfacing that server-side — a ping with poor HDOP under a bridge or inside a parking structure is technically valid data but not something you want snapping a vehicle's icon onto the wrong street.
Getting the Fix Off the Vehicle: GSM/GPRS
The GSM/GPRS (or LTE Cat-M1, on newer hardware) module turns a local coordinate into something your server can see. The device either opens a persistent TCP socket to a fixed IP/port and streams pings as they are captured, or issues an HTTP POST per ping or per batch to a REST endpoint. TCP keeps latency and per-ping overhead lower; HTTP is easier to secure with TLS and easier to debug with standard tooling.
Cellular data is metered, so most device protocols pack a ping into a compact binary or delimited-text frame rather than verbose JSON over the wire — the ingest layer expands it into a normalized record once it lands on the server.
{
"deviceId": "TRK-10422",
"lat": 23.022505,
"lng": 72.571362,
"speedKph": 42.5,
"headingDeg": 187,
"timestampUtc": "2024-03-24T09:14:32Z",
"ignitionOn": true,
"hdop": 1.2
}
Ingesting and Storing Pings on the Server
On the backend, a listener — a raw TCP server for socket-based devices, or a controller action for HTTP-based ones — parses the incoming frame, validates the device ID against a known fleet, and writes a row per ping into a table indexed on device ID and timestamp. High-volume fleets sometimes move this into a purpose-built time-series store once per-device ping rates get high.
This is also where you handle out-of-order and duplicate pings. A device that loses signal buffers pings locally and bursts them out once it reconnects, so timestamp each ping with the device's own clock rather than the server's receive time, or your speed and route calculations end up wrong.
[ApiController]
[Route("api/pings")]
public class PingController : ControllerBase
{
private readonly IHubContext<TrackingHub> _hub;
private readonly ITrackingRepository _repo;
public PingController(IHubContext<TrackingHub> hub, ITrackingRepository repo)
{
_hub = hub;
_repo = repo;
}
[HttpPost]
public async Task<IActionResult> Ingest([FromBody] LocationPing ping)
{
if (Math.Abs((DateTime.UtcNow - ping.TimestampUtc).TotalMinutes) > 5)
return BadRequest("Ping timestamp out of tolerance.");
await _repo.SavePingAsync(ping);
await _hub.Clients.Group(ping.DeviceId)
.SendAsync("locationUpdated", ping);
return Accepted();
}
}
Getting the Dot to Move: The Map Client
The web UI does not poll a database for every frame of movement — it subscribes to a hub (SignalR is the natural fit in an ASP.NET Core stack) that the ingest layer pushes new pings into as they arrive, and the map just moves or interpolates the marker on each message. Short-interval polling every 5-10 seconds is a reasonable fallback if you do not need sub-second updates.
- deviceId — which vehicle sent the ping
- lat / lng — the fix itself
- speedKph and headingDeg — direction of travel
- timestampUtc — the device's own clock, not the server's
- ignitionOn — useful for idle vs. moving state
- hdop — fix quality, for filtering noisy readings
Devices with a battery backup will happily send pings with a stale or wildly wrong timestamp right after a hard power cut. Reject or flag any ping whose timestamp is more than a few minutes off from the server's clock rather than trusting it blindly.
Wrapping Up
None of the individual pieces here are exotic — the difficulty is almost always in the seams: clock handling between device and server, reconnection and out-of-order delivery, and keeping the map client from redrawing more often than the data actually changes.