Skip to content

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.