flightrelay 0.1.7

Flight Relay Protocol (FRP) — golf launch monitor event streaming over WebSocket
Documentation

flightrelay

Rust SDK for the Flight Relay Protocol (FRP) — golf launch monitor event streaming over WebSocket.

CI crates.io

What's in the box

  • Message types — strongly typed Rust representations of every FRP message, event, and command
  • Unit-tagged valuesDistance and Velocity types that serialize as unit-tagged strings ("180.5m", "67.2mps") with full unit conversion
  • Shot accumulatorShotAggregator collects lifecycle events (shot_triggerball_flight + club_path + face_impactshot_finished) and yields a composed CompletedShot
  • WebSocket transportFrpClient and FrpListener/FrpConnection handle the FRP handshake and provide synchronous send/recv

Features

Feature Description
(default) Message types, parsing, unit-tagged values
controller ShotAggregator for accumulating shot lifecycle events
device Reserved for future device-side helpers
client FrpClient — WebSocket client (connects to a device)
server FrpListener / FrpConnection — WebSocket server (accepts controllers)

The client and server features add a dependency on tungstenite.

Usage

Controller (receiving shots)

use flightrelay::{FrpClient, ShotAggregator, FrpMessage};

// Connect to a device and perform the FRP handshake
let mut client = FrpClient::connect("ws://192.168.1.50:5880/frp", "My App")?;
let mut shots = ShotAggregator::new();

loop {
    let msg = client.recv()?;
    if let FrpMessage::Envelope(envelope) = msg {
        if let Some(shot) = shots.feed_envelope(&envelope) {
            println!("{}: carry {}", shot.device, shot.ball.unwrap().carry_distance.unwrap());
        }
    }
}
[dependencies]
flightrelay = { version = "0.1", features = ["client", "controller"] }

Device (emitting shots)

use flightrelay::{FrpListener, FrpEnvelope, FrpEvent, ShotKey, BallFlight, Velocity, Distance};

// Listen for controllers
// Bind to the default FRP port (clients connect to ws://host:5880/frp)
let listener = FrpListener::bind("0.0.0.0:5880", &["0.1.0"])?;
let mut conn = listener.accept()?;

// Send a shot
let key = ShotKey::new(1);
conn.send_envelope(&FrpEnvelope::new("MyDevice-001", FrpEvent::shot_trigger(key.clone())))?;
conn.send_envelope(&FrpEnvelope::new("MyDevice-001", FrpEvent::ball_flight(
    key.clone(),
    BallFlight {
        launch_speed: Some(Velocity::mps(67.2)),
        carry_distance: Some(Distance::meters(180.5)),
        ..Default::default()
    },
)))?;
conn.send_envelope(&FrpEnvelope::new("MyDevice-001", FrpEvent::shot_finished(key)))?;
[dependencies]
flightrelay = { version = "0.1", features = ["server"] }

Messages only (no WebSocket)

use flightrelay::{FrpMessage, Distance};

let msg = FrpMessage::parse(r#"{"device":"EagleOne-X4K2","event":{"kind":"ball_flight","key":{"shot_id":"...","shot_number":1},"ball":{"carry_distance":"180.5m"}}}"#)?;
let json = msg.to_json()?;

// Unit conversion
let d = Distance::meters(180.5);
assert_eq!(d.as_yards(), 197.4); // approximate
[dependencies]
flightrelay = "0.0.1"

Protocol

FRP is a minimal WebSocket protocol. After a start/init handshake, the device streams JSON events for each shot lifecycle stage:

shot_trigger → ball_flight + club_path + face_impact (any order) → shot_finished

Events arrive as they become available — ball flight data may arrive immediately while club and face impact data take longer to process. Controllers accumulate events by shot_id until shot_finished.

See the full spec for details.

License

Licensed under either of Apache License, Version 2.0 or MIT License, at your option.