Read in another language: English (this document) · Français.
Shared C++ library for LAN supervision of desktop applications.
morfBeacon lets an application (ComponentHub, SiteWatch, GatewayLab, and future tools) announce its presence on the local network and expose its metrics to a supervisor (morfDashboard), in a few lines of code.
Two distinct needs, two mechanisms:
| Need | Mechanism | Who talks | Frequency |
|---|---|---|---|
| "Is the app alive?" | UDP broadcast heartbeat | the app emits | every 15 s |
| "What is its detailed state?" | HTTP /status server |
the supervisor asks | on demand |
The supervisor scans nothing: it listens passively. No IP to know, no
configuration, automatic discovery. An app that stops emitting for ~1 minute is
considered offline. Detailed metrics never travel in the heartbeat: they stay
behind /status, queried only when needed.
Proven on a heterogeneous fleet (23 July 2026). Distributed discovery works across Windows, Linux, Raspberry Pi and ESP32 simultaneously, with no manual configuration: morfMonitor instances discover one another, and services published by the Raspberry Pi appear automatically on Windows and the other way round. No IP address is written anywhere. The independent implementations of the protocol - this Qt library, the Arduino emitter, morfSync in native C++ and morfDashboard in Python - interoperate on the same network, which is what the checker below exists to keep true.
UDP heartbeat (compact JSON, broadcast on port 45454):
{
"proto": "morfbeacon/1",
"app": "ComponentHub",
"host": "fredpc",
"version": "1.6.0",
"state": "ok",
"status_port": 8787,
"instance": "ComponentHub@fredpc",
"uptime_s": 3600,
"ts": 1752400000
}HTTP GET /status (on status_port):
{
"app": "ComponentHub",
"host": "fredpc",
"version": "1.6.0",
"state": "ok",
"uptime_s": 3600,
"metrics": { "components": 812, "projects": 14, "last_backup_age_s": 7200 },
"ts": 1752400000
}GET /healthz returns {"status":"ok"} (a lightweight liveness probe). The full
protocol is specified in docs/fr/PROTOCOL.md (FR).
An application that serves a web interface declares it by setting one field:
cfg.webUiPath = "/"; // the whole declaration
cfg.webUiLabel = "Analyses météo"; // optional, defaults to appName
cfg.webUiDescription = "Tendances et corrélations."; // optional
// cfg.webUiPort = 8080; // optional, defaults to statusPortThe capability web_ui is then added automatically to the heartbeat, and the
detail appears in /status:
"capabilities": ["web_ui"],"web_ui": { "path": "/", "label": "Analyses météo", "port": 8799 }A consumer discovers the service, sees the capability, fetches /status once
to learn how to open it, and offers a link - without knowing the application.
Adding a new service to the ecosystem therefore requires no change to any
consumer.
The capability is derived from webUiPath, never declared separately: the
detail and the capability that makes it discoverable cannot drift apart.
#include <morfbeacon/PresenceService.h>
#include <morfbeacon/IMetricsProvider.h>
morfbeacon::PresenceConfig cfg;
cfg.appName = "ComponentHub";
cfg.version = APP_VERSION;
morfbeacon::FunctionMetricsProvider provider([this] {
QJsonObject m;
m["components"] = m_inventory->count();
return m;
});
auto* presence = new morfbeacon::PresenceService(cfg, &provider, this);
presence->start();Detailed guide (CMake + where to wire the code in ComponentHub / SiteWatch): docs/fr/INTEGRATION.md (FR).
Same commands as ComponentHub / SiteWatch:
# Windows (MSYS2 / MinGW)
cmake --preset mingw
cmake --build --preset mingw
# Linux / Raspberry Pi
cmake --preset linux # or linux-arm64
cmake --build --preset linuxIn a standalone build, the morfbeacon_demo example is compiled
(build-*/examples/minimal/).
In two terminals:
# 1. the "fake app" (emits the heartbeat + exposes /status)
./build-mingw/examples/minimal/morfbeacon_demo
# 2. the "fake dashboard" (listens + polls), no dependency
python tools/fake_dashboard.py --poll --every 10The tester shows discovery, offline transitions and metrics. It reproduces exactly what morfDashboard does.
The protocol now has five implementations, forced by platform and language boundaries: two emitters (Qt, ESP32) and three listeners (Qt, ESP32, Python). None can be shared with the others, so what is common is a format - and a format without verification drifts in silence. A renamed field, an integer sent as a string, a misspelt capability, and a consumer stops seeing a service with nothing to report it.
tools/check-protocol.py is the executable reference for that format. Run on
the local network, it validates the real datagrams of every implementation
at once:
python tools/check-protocol.py # listen 20 s, validate everything
python tools/check-protocol.py --seconds 60
python tools/check-protocol.py --no-pull # skip the /status requests
python tools/check-protocol.py --file capture.jsonIt changes nothing and emits nothing: it listens, reports, explains. Exit status is non-zero on any violation, so it can gate a release.
Its most valuable check is not on either document but between them: a
service announcing the web_ui capability must serve a web_ui block in
/status. Declaring one without the other produces an interface nobody can
open - a defect that occurred twice in the ecosystem, both times in a service
that reimplements its own /status and forgot the block.
Standard library only, like fake_dashboard.py.
Qt 6 (Core + Network only - no Widgets, usable in a headless service). C++17, CMake ≥ 3.21.
The in-depth guides are in French under docs/fr/; an
English index is at docs/en/.
| Document | Contents |
|---|---|
| docs/fr/PROTOCOL.md (FR) | The morfbeacon/1 wire protocol (heartbeat, /status, ports) |
| docs/fr/ARCHITECTURE.md (FR) | The classes (Heartbeat, StatusServer, providers, PresenceService) |
| docs/fr/INTEGRATION.md (FR) | Integrating morfBeacon into an application |
| CHANGELOG.md | Version history |
| ROADMAP.md | Planned work |
| CONTRIBUTING.md | Contribution guide |
Distributed under the GPL-3.0-only license. © 2026 morfredus.