Fleet — several robots on one floor¶
Two robots on the same floor do not collide because they talk to each other. They do not collide because each chassis stops in front of an obstacle. That is worth saying first, because everything on this page sits above that line: this layer arranges, it does not brake. A radio failure must never stop operations, and a robot that hears nobody drives exactly as it did before the fleet existed.
What the fleet layer buys is not safety. It is that two robots do not drive to the same spot, wait for each other, and turn a corridor into a traffic jam that a human has to resolve.
Status
In operation on three devices since 25.08.2026 (Stuhr). Measured, not estimated: every number on this page comes from a run log, and the date is given with it.
The heartbeat¶
Every device broadcasts a signed heartbeat over UDP to the other devices of its group. The heartbeat carries who is sending, where it stands, what it currently claims, and since when. Nothing else — no commands, no map data, no orders.
| Field | Meaning |
|---|---|
kennung |
serial number of the chassis; this is the identity |
ort |
pose in metres, from the chassis |
mittel |
the resource currently claimed, or nothing |
phase |
angemeldet (registered), unterwegs (on the way), besetzt (standing on it) |
seit |
when this claim was made |
fassung |
software version, for display only |
The beat runs every two seconds (FLOTTE_TAKT_MS, default 2000). Until
26.08.2026 it was five seconds; the change came out of a field observation that
decisions between two stops took visibly too long. Releasing a target was only
learned by the others with the next beat — on average two and a half seconds,
at every stop of every round.
More important than the beat itself: every change of intent is sent immediately. Whoever releases a target says so at the moment of releasing it. The regular beat is now only the safety net for a lost packet.
A device that has been silent for 30 seconds (AUSFALL_NACH_MS) counts as
gone, and its claims fall away with it. Thirty seconds is fifteen missed beats.
That sounds generous and is deliberate: a hospital corridor loses a packet now
and then, and a master that changes at every radio hole is worse than none.
Who has the say¶
The device with the smallest serial number is master. There is no election with votes.
That is a deliberate simplification, and it is worth knowing why. A proper election algorithm (Bully, Raft) guarantees that there is never more than one master. We do not need that guarantee, because no master does anything irreversible — it hands out reservations with an expiry. If the network splits, the expiries run out and both halves drive alone again. "Smallest serial number" is explained in ten lines, and a technician standing in the corridor can work it out.
Known consequence, and we intend to fix it
Ordering by serial number alone means the device with the highest number
loses every race it enters — systematically, not occasionally. Measured on
26.08.2026: of eleven fallback placements in eleven minutes, ten came from
the same device. Eric Cordero raised the same point independently from the
lift queue. The agreed direction is to order by (priority, claimedAtMs,
serial) — claim time first, serial only to break ties. See
Lift Control.
What gets reserved, and under which name¶
A stop of a tour becomes an operating resource: punkt:<map name>/<point name>,
for example punkt:eg/buero 2.
Not the point id. Until 26.08.2026 the key was the identifier the chassis gave the map point, and that is the mistake this section exists to prevent. The id belongs to the map, not to the place. Copy a map to a second device — and that is exactly how they are delivered — and the same corridor point carries a different id there. Both robots then reserve diligently, each something different, and drive onto the same spot unbraked. Nothing fails visibly: there is no error message, only two devices that suddenly come too close.
Map name and point name are what a human finds identical on both devices. Both are carried along when copying, neither is reassigned.
The side effect was wanted: the occupancy list now reads punkt:eg/buero 2
instead of punkt:34. Whoever reads the log knows where that is without
looking it up.
The charging station is a resource too — lade:<map name>/<number>. Until
26.08.2026 it was the only place approached without any reservation. With one
robot that never shows. With three it shows in the evening, when all of them
finish at roughly the same time, read the same map, find the same first station
and drive there.
The decision¶
Before setting off, a device registers its target and asks whether it may go. The answer is one of four:
| Answer | Meaning |
|---|---|
frei |
nobody else holds it and there is room |
besetzt |
someone stands on it or is driving to it — regardless of rank |
nachrang |
both only registered; the other has precedence |
zu-nah |
formally free, but a device is standing within the clearance |
besetzt beats rank in both directions. Whoever is already there keeps it, even
against a smaller serial number: sending the standing device away means clearing
the spot twice instead of once.
Space around the target¶
Independent of any reservation, a target counts as blocked while another device
stands within FLOTTE_RAEUM_M of it (default 2.0 m). Somebody who has
formally released a point is still standing next to it for a while — the point
is occupied, not on paper, but in sheet metal.
This value belongs to the floor plan, not to the software
Measured on the test tour in Stuhr on 26.08.2026: the three stops lie 2.52 m, 2.82 m and 3.47 m apart. At a clearance of 2.0 m, a robot that misses its point by half a metre — which is normal — blocks the neighbouring point for everyone else, although it does not want it. Of 29 waits in ten minutes, 20 had exactly this cause.
Rule of thumb: clearance below the smallest distance between two stops, minus twice the positioning tolerance. Set it in the environment, not in the code.
When the wait is over¶
After FLOTTE_WARTEN_S (default 60 s) a device stops waiting and parks one
metre in front of the occupied target, then continues its tour. The fallback
spot is not reserved — otherwise the one giving way would block the point for
everybody else as well.
The direction of that metre is measured towards the device's own next target first, and only then towards its current position. Two reasons, both from the field: standing closer than one metre to the blocked point produced no fallback spot at all and the stop was dropped; and whoever approaches from the same direction as its predecessor parks squarely in its way out.
The race nobody sees¶
Two devices registering in the same millisecond have not heard each other yet.
Both are told "free", both go to unterwegs, both set off. The rule did not
fail; it decided with knowledge from a moment ago.
The first fix considered — wait one heartbeat round before departing — was discarded: it would have lengthened every stop, including the ninety-nine in a hundred where no double claim exists.
What is implemented instead: set off as before, and check while driving. The other device's message arrives during the trip, and then the lower-ranked one steps aside. Precedence goes to whoever is already standing there, then to the smaller serial number. The normal case costs nothing — only memory is read.
Settings¶
All of these live in the start environment, not in the code, because all of them depend on the site rather than on the software.
| Variable | Default | What it does |
|---|---|---|
FLOTTE_SCHLUESSEL |
— | shared secret; without it the device works alone |
FLOTTE_GRUPPE |
standort |
the site; devices only hear their own group |
FLOTTE_TAKT_MS |
2000 |
heartbeat interval |
FLOTTE_RAEUM_M |
2.0 |
clearance around a target |
FLOTTE_FREIGABE_M |
1.5 |
beyond this distance an besetzt counts as abandoned |
FLOTTE_WARTEN_S |
60 |
how long to wait before giving way |
FLOTTE_ANSCHLUSS |
45301 |
UDP port |
FLOTTE_ADRESSEN |
— | fixed peer addresses; empty means broadcast |
The key never appears in a log, in the technician page or in an API response — only whether one is set.
What the numbers looked like¶
Field test Stuhr, three devices on one tour with three stops 2.5 to 3.5 m apart — deliberately the hardest case, and one no customer site has.
| Measured | 25.08.2026 | 26.08.2026 morning | 26.08.2026 after tuning |
|---|---|---|---|
| gap between two stops | — | ~44 s | 5.7 / 8.8 s |
| cycle per fallback placement | — | 74 s | 26 s |
| discarded packets | 0 | 0 | 0 |
| collisions | 0 | 0 | 0 |
The acceptance rule for this test was set by the CEO and is not negotiable: the fleet test passes only if the robots do not collide with each other. "Almost" does not count.