Autonomous Golf Cart Fleet Control
The fleet control system that gets driverless carts through a full day of operation
- 01 카트 · ACU
- MQTT 02 수신 · 정규화
- 03 플릿 관제 백엔드
- 04 관제 화면 · HMI
- 05 운영자
Scope
Making the cart drive is the vehicle control unit's job; this system's job is getting the fleet through a day — which map each cart carries, whether headway is right, what to raise when something goes wrong. I defined what the vehicle reports and in what shape, negotiated the spec with the manufacturer and the institute, built the control frontend and in-cart HMI directly, and delegated backend implementation against defined completion criteria.
One normalization layer for two firmware versions
Carts on firmware v1 and v2.1 connect to the same server with different topic structures and payloads. Rather than demanding vehicle-side changes, the server absorbs the difference: a single ingest point identifies each message's dialect and converts it to one canonical contract, so domain logic never knows versions exist. The legacy codec is frozen; the new one is generated from a definition file. The same point drops duplicate message ids and rejects sequence regressions — while accepting a new boot id, a large backward jump, or a long silence as a fresh session, an exception added after watching a device counter wrap in the field.
Command loss that was recorded as success
Commands to new-version carts once went out on old-version addresses; 797 were lost while the logs read as normal, discovered only by analyzing a separate rejected-message log. Downlink addressing now follows the dialect each cart declares on its uplink, unsupported commands are rejected before publish, and failure states are recorded separately. Unanswered commands are re-sent up to three times at six-second intervals before a timeout is declared; a re-send keeps the same command id so a cart that already handled it returns its result instead of acting twice. Command sequence numbers are issued monotonically, so a late-arriving old command is refused by the cart.
Emergency stop as its own delivery class
Treating every command alike puts emergency stop through the same gates as the rest — blocked for carts judged offline, refused for carts whose dialect is unknown. Delivery was split by class instead: emergency stop bypasses every pre-gate, reaches offline carts, and is published across all known dialects when the dialect is unknown, while other commands are withheld with a recorded reason. Facility-wide broadcast is open to emergency stop alone. There is no automatic resume — neither restored comms nor a recovered device resumes a cart; only an explicit per-cart command does, and a broadcast resume is ignored by the vehicle.
Control informs; the vehicle owns safety
Headway is published as an information stream that expects no response — never a command — because server data alone must not close a control loop. Position is projected onto the course path to rank vehicles, carts on other courses count as candidates within 4 m laterally, oncoming traffic is separated by heading angle, and loop courses wrap. The verdict separates three cases: ahead and none are stated explicitly every cycle, while unknown sends nothing at all. The receiving side is specified too — a cart must not hold the last value indefinitely and drops it from its inputs after three times the publish period, with the spec noting that dropping the input does not mean stopping.
Two layers that keep disconnected carts out of the verdict
A cart that loses comms still leaves its last value behind, and judging on it puts a phantom leader in front of the cart behind. Liveness and position freshness are kept as separate values; offline is decided by both the broker's will message and an aging watch at fifteen seconds; headway drops any cart unheard from for ten. When applying a speed setting, offline status is checked before speed — a stale 0 km/h would otherwise read as stopped and let the change land on a cart that is actually moving, and unknown speed is treated as moving rather than stopped.
A reconciliation loop for devices that forget
On the premise that a cart forgets its settings when it reboots, the server owns operational state and re-issues it on setting changes, reconnection, and course changes, backed by a five-minute background pass so that missing all three hooks bounds the drift to one cycle. Safety gates apply changes only at standstill or at hole boundaries, so state changes never alter behavior mid-drive.
One ingest point, and a deployment that gives up zero downtime
Scaling the server out multiplies broker subscribers, and two receivers mean double writes and double alarms — which is exactly what a rolling deploy creates during cutover. Telemetry ingest is therefore pinned to a single point, a wrong combination fails at startup, and the deployment topology is fixed by a test; releases accept a brief interruption instead of overlapping subscribers. Shared subscription is defined in the spec but unused, because there is no reason to split ingest at this fleet size and splitting it would mean moving state outside the process. Writes are batched at 200 rows or one second, backlog is capped with the oldest rows dropped and the discarded count logged, and both the cart's timestamp and the server's are stored — trajectory and history use the cart's, liveness and command timeouts use the server's, and the skew between them is tracked per cart.
Map pipeline with three-layer validation
Drone orthophotos were tried first for source coordinates, but their error was too large, so coordinates are captured by actually driving the cart — the map and the vehicle share one coordinate frame. Validation runs in three layers: conflicting segments cannot be authored at all, connectivity is checked automatically, and a simulator carrying the real cart's turning radius drives the map to catch curves that look fine on paper but cannot be driven.
Resolving a semantic mismatch in speed commands
The server pre-computed "20% slower than 10 km/h" into an absolute 8 km/h while the cart applied the percentage again — both sides correct by their own definition. Judged a matter of meaning rather than a wrong value, the command was unified as a percentage with the cart as the computing side, written into the spec. Specs have defined meaning before format ever since.
Letting the other side check for itself
The most common sentence in device integration is "we sent it." Raw MQTT traffic is kept in both directions and opened for query on screen, including messages that failed validation and why they were dropped. Connection guidance is not a document — the same code that publishes builds the addresses it hands out, so guidance and implementation cannot drift. A case where old-version addresses were handed out and downlink never arrived is what produced this.
Status
Verified complete by the manufacturer and the research institute across a test track and a real golf course with three vehicles, and since extended to run two facilities at once. Protocol, control core, map pipeline, and ingest are running. Ahead of the pilot, broker topic authorization and the vehicle-side stop threshold on comms loss remain to be settled; the latter is on the agenda with the vehicle manufacturer.