Case · onboard systems
Live video from vessels that keep losing the link
An industrial control company needed to watch, from shore, the cameras on fishing vessels working hundreds of kilometres off the coast. The problem was not the video: it was the link, which drops several times a day. This is the path the signal takes and why it is built this way.
Why this case has no screenshots
The system lives in an industrial PC on board, in a satellite link and in a server on shore. A screenshot of the operator's screen would show a video player, which is exactly the easy part. What is really worth showing is the path the signal takes: what talks to what, over which protocol, and what happens when the boat loses its link.
The diagram draws itself, in six chapters. Watch the moment the link line breaks and turns red: that happens for real, and it explains almost every technical decision in the project.
Swipe the diagram sideways to see all of it.
- video
- control and telemetry
- data on disk
-
1 · The camera speaks more than one language
The cameras on board give video over RTSP, but snapshots and events need the vendor's native SDK, which is a C library. The agent calls it from Python with ctypes. ONVIF is there to discover them and configure them.
-
2 · The agent decides what goes out, and when
A process on the vessel's industrial PC, written in Python and with a version in Go. It keeps state in a local SQLite database because the boat spends hours without a link: whatever could not be sent waits there instead of being lost.
-
3 · The real problem: the link drops
Between the boat and the coast there is satellite or intermittent 4G, with packet loss and high latency. A protocol on top of TCP stalls there. That is why the video goes out over SRT, which runs on UDP and recovers what was lost without blocking the rest. The agent pushes it with FFmpeg in sender mode.
-
4 · On shore, translate it for the browser
The SLATE server listens in receiver mode, picks up the stream and transcodes it to HLS: segments and a playlist that any browser understands without plugins. The snapshots are stored there too.
-
5 · Control travels by another route
Commands and telemetry do not travel with the video: they use MQTT over Mosquitto, with topics per device and per camera. It is light and it survives disconnections. The core is an API with a hexagonal architecture, with a domain that does not import infrastructure at all, and PostgreSQL for the fleet.
-
6 · And only then, the screen
The operator opens the browser and sees the camera of a boat that is hundreds of kilometres away. Everything before it exists so that last step looks trivial.
The four decisions that hold it up
-
Transport
SRT instead of TCP
On a lossy link, a transport that retries in order stalls and the picture freezes. SRT runs on UDP and recovers what was lost without blocking what is already coming behind it: the quality degrades, not the session.
-
No coverage
A queue inside the boat
The boat spends hours without a link. Whatever could not be sent waits in an SQLite database on board and goes out when the signal comes back, instead of being lost or blocking the agent.
-
Two routes
Control does not travel with the video
Commands and telemetry go over MQTT, with one topic per device and camera. If the video drops, the boat still answers; and a command weighs a few bytes, not a few megabytes.
-
Core
The domain knows nothing about cameras
The API is hexagonal: the domain imports no infrastructure. Changing the broker, the database or the camera vendor touches the adapters, not the business rules.
A whole fleet does not fit on one server
Video does not transcode itself: each server on shore handles a limited number of recordings at a time, and past that number it runs out of hardware. So growth is not buying a bigger server, it is adding one slave server per batch of recordings, each with its own group of vessels assigned to it.
That is how the system goes from watching a few boats to watching the whole fleet: adding boats means adding servers, with the same core, the same broker and the same screen for the operator. Everything else along the path — the agent on board, SRT, the local queue, MQTT — is already built for that: each boat talks to its own server, not to all of them.
Where each piece of the diagram comes from
The path is not an idealisation: every element in the drawing is taken from the code of the system, not from how it ought to be.
| RTSP and ONVIF to the cameras | addresses and client in the device agent |
|---|---|
Native SDK through ctypes | the C headers of the vendor's library |
| Local queue in SQLite | the agent's persistence layer |
| SRT output with FFmpeg | srt://host:port?mode=caller in the agent |
| Listening on shore | srt://0.0.0.0:port?mode=listener in the video server |
| Transcoding to HLS | hls_time, hls_list_size and hls_flags parameters |
| MQTT topics | streaming/device/{id}/camera/{uuid}/… |
| Hexagonal API | the domain, application and infrastructure folders |
Do you have a system that lives far from the keyboard?
Devices in the field, links that drop, data that cannot be lost and someone who needs to see it on a screen. It is the work I like most.
Let's talk →