Connected Vehicle Telemetry and Remote Commands with NATS
An automotive use case combining core pub/sub telemetry streaming from a connected car with request/reply for remote commands (like remote-lock) that need a guaranteed acknowledgement.
- nats
- automotive
- iot
- request-reply
- tutorial
The scenario
A connected-vehicle platform has two very different communication needs on the same car: continuous telemetry (speed, battery level, location — fire-and-forget, high frequency) and occasional remote commands (lock the doors, honk the horn — must know for certain whether the car actually received and executed it).
Using the wrong pattern for either would be a mistake: request/reply for telemetry would force the app to wait on thousands of tiny round-trips it doesn't need acknowledged; pub/sub for a lock command would leave the app with no way to know if the door actually locked.
Step 1 — start NATS
docker run -d --name nats-server -p 4222:4222 -p 8222:8222 nats:latest
nats context add local --server="nats://localhost:4222" --selectStep 2 — telemetry: plain pub/sub, high frequency, no reply needed
nats sub "telemetry.VIN123"for i in $(seq 1 5); do
nats pub telemetry.VIN123 "{\"speed\":$((30+i)),\"battery\":78,\"lat\":37.77,\"lon\":-122.41}"
doneNone of these publishes wait for anything — that's correct for high-frequency telemetry, where an occasional dropped reading is acceptable and blocking on an ack per message would add needless latency and complexity.
Step 3 — remote lock command: request/reply, must be confirmed
nats reply "command.VIN123.lock" '{"result":"locked","batteryImpact":"negligible"}'nats request "command.VIN123.lock" '{"requestedBy":"owner-app"}' --timeout=5sThe app only shows "🔒 Locked" in its UI after this reply arrives — never optimistically, since a car with no connectivity should surface as a failure, not a false success.
Step 4 — the car is offline: the command must fail loudly, not silently
nats request "command.VIN456.lock" '{"requestedBy":"owner-app"}' --timeout=3snats: error: no responders available for requestThe app must surface this as "Couldn't reach your car — try again when it has signal," rather than silently retrying forever or falsely reporting success.
Step 5 — combining both: telemetry confirms the command's real-world effect
For extra confidence beyond the command reply itself, the app can also watch telemetry for the resulting state change — useful since a reply only confirms the car received the command, while telemetry confirms the physical outcome:
nats pub telemetry.VIN123 '{"speed":0,"battery":78,"doorsLocked":true}'Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| App shows "locked" but the car never actually locked | App treated the request/reply's success as final truth without also checking telemetry-confirmed physical state | Treat the reply as "command received," and optionally cross-check the next telemetry update for the actual state change on safety-critical commands |
| Telemetry dashboard misses occasional readings | Expected — plain pub/sub is fire-and-forget by design; a dropped reading here is low-stakes | Don't add request/reply overhead to high-frequency telemetry; if historical completeness matters, use JetStream instead of upgrading to request/reply |
| Remote command hangs the app's UI indefinitely | No --timeout set on the request | Always set an explicit timeout appropriate to a "car might be offline" scenario (a few seconds, not indefinite) |
| Two different remote commands appear to cross-talk | Both commands published to the same subject instead of a command-specific one | Use one subject per command type per vehicle, e.g. command.<vin>.lock vs command.<vin>.honk |
Recap
| Need | Pattern | Why |
|---|---|---|
| High-frequency telemetry | Core pub/sub (nats pub/nats sub) | Fire-and-forget is correct — no per-message ack overhead |
| Remote command (lock, honk) | Request/reply (nats request/nats reply) | The app needs to know definitively whether the car received it |
| Car offline | no responders available error | Must be surfaced to the user, never silently retried forever or assumed successful |