How we built an automated beach flag system integrating NWS API data, stepper motor control, and smart polling strategies. Production IoT with Rust's safety guarantees.
Automating Beach Safety
Panama City Beach uses colored flags to communicate ocean conditions. Double red means the water is closed. Red means high hazard. Yellow means moderate surf. The flags are raised manually by lifeguards checking conditions and weather reports—a process prone to delays and inconsistency.
We built a system that monitors National Weather Service data and automatically adjusts flags. The goal was eliminating the gap between dangerous conditions and visible warnings.
The Architecture
The system has three layers: data ingestion from external weather APIs, decision logic that maps conditions to flag colors, and hardware control that physically moves the flags via stepper motors.
This separation matters. The decision logic doesn't know about HTTP or motors. The hardware layer doesn't know about weather. Each component has a single responsibility and can be tested independently.
Parsing Uncertainty
Weather forecasts come as natural language: "Seas 3 to 5 feet, winds 15 to 20 mph, moderate rip current risk." Converting this to structured data requires parsing—a task that's easy to get mostly right and hard to get completely right.
We settled on conservative parsing. When the text is ambiguous, assume the worse interpretation. If we can't parse surf height, treat it as elevated. False alarms are acceptable; missed warnings are not.
The parsing rules emerged iteratively from real NWS data. Edge cases appeared gradually: unusual phrasing, regional terminology, forecast updates that contradicted earlier predictions. Each edge case became a test case.
Decision Thresholds
Flag colors map to condition thresholds. High rip current risk means double red. Surf over six feet means double red. Surf over four feet means red. Moderate rip current means yellow.
These thresholds aren't obvious—they encode domain knowledge about what conditions are dangerous for swimmers. We consulted lifeguards and beach safety literature to calibrate them.
The thresholds are configurable because different beaches have different profiles. A threshold appropriate for a shallow, protected bay differs from one for an exposed coastline.
Adaptive Polling
The NWS API has rate limits. We can't poll every second. But conditions can change quickly during storms, and we want timely flag updates.
The solution is adaptive polling: check more frequently when conditions are elevated or changing. During calm weather, poll every fifteen minutes. During dangerous conditions, poll every five minutes. At night when the beach is closed, poll hourly.
This trades off API quota against responsiveness. The tradeoff is explicit and tunable.
Hardware Abstraction
The flag mechanism uses stepper motors that rotate to different positions for different colors. The hardware interface is abstracted behind a trait: set flag color, get current flag, run self-test.
This abstraction enables testing without hardware. We can substitute a mock implementation that records commands and returns expected responses. The core logic doesn't know the difference.
When hardware failures occur—and they do, in outdoor environments—the abstraction contains the failure. The system can detect that the motor isn't responding and alert operators without crashing the decision logic.
Why Rust for IoT
Rust provides several properties valuable for embedded and IoT systems.
Predictable resource usage means knowing exactly how much memory the system needs. There's no garbage collector that might pause at an inconvenient moment or gradually accumulate memory pressure.
Null safety means Option types force handling of missing data explicitly. In a safety-critical system, an unhandled null that crashes the process could leave flags in the wrong position.
Compile-time concurrency checking means the web dashboard and background flag updater can share state without data races. The borrow checker catches race conditions before deployment.
Cross-compilation means the same codebase runs on development machines and production hardware. Testing locally provides confidence that works on the actual deployment target.
Operational Lessons
The system has run for months with high uptime. The failures that did occur were instructive.
Network outages are the primary failure mode. When the system can't reach the NWS API, it must decide what to do. We chose conservative fallback: if data is stale beyond a threshold, raise the flag level. Better to over-warn than under-warn.
Hardware occasionally fails in humidity and salt air. Redundant sensors and motor position feedback detect failures quickly. Alert thresholds tune the tradeoff between nuisance alerts and missed problems.
The human in the loop remains essential. The system automates the routine case but defers to lifeguards for judgment calls. Automation handles the common case; humans handle the edge cases.
The Broader Pattern
This project illustrates a pattern for safety-critical automation. The system isn't autonomous—it's assistive. It does the tedious work of monitoring conditions continuously, freeing humans to focus on swimmers rather than weather apps.
The design biases toward safety over accuracy. False positives (unnecessary warnings) are accepted; false negatives (missed dangers) are not. This asymmetry shapes every threshold and fallback decision.
Physical systems fail in ways software systems don't. Moisture, vibration, temperature extremes, vandalism—the failure modes are concrete and varied. Robust IoT systems assume components will fail and design for graceful degradation.
Beach visitors see accurate, timely warnings. Lifeguards focus on watching water instead of checking forecasts. The system is invisible when it works—which is exactly the goal.