Skip to main content

BinaryConnection

Struct BinaryConnection 

Source
pub struct BinaryConnection<S: Read + Write> { /* private fields */ }
Expand description

Binary protocol connection to a Mevo+ device.

Generic over S: Read + Write so callers can use any stream type. For non-blocking usage, configure the stream’s read timeout externally and call recv() — it returns Ok(None) on WouldBlock/TimedOut instead of blocking.

§Example (TcpStream)

use std::time::Duration;
use ironsight::{BinaryConnection, ConnError};

let mut conn = BinaryConnection::connect(ironsight::conn::DEFAULT_ADDR)?;
conn.stream_mut().set_read_timeout(Some(Duration::from_millis(100)))?;
loop {
    match conn.recv()? {
        Some(env) => println!("{:?}", env.message),
        None => { /* send keepalive */ }
    }
}

Implementations§

Source§

impl<S: Read + Write> BinaryConnection<S>

Source

pub fn new(stream: S) -> Self

Wrap any Read + Write stream as a binary protocol connection.

Source

pub fn stream(&self) -> &S

Borrow the underlying stream (e.g. to query peer address).

Source

pub fn stream_mut(&mut self) -> &mut S

Mutably borrow the underlying stream (e.g. to set read timeout).

Examples found in repository?
examples/event_loop.rs (line 188)
150fn run() -> Result<(), ConnError> {
151    // 1. Connect
152    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
153    println!("Connecting to {addr}...");
154    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
155    println!("Connected.");
156
157    // Wire-level trace callbacks.
158    conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
159    conn.set_on_recv(|env| println!("\n<< {env:?}"));
160
161    // 2. Handshake (blocking via drive())
162    println!("DSP sync...");
163    let dsp = seq::sync_dsp(&mut conn)?;
164    println!("AVR sync...");
165    let avr = seq::sync_avr(&mut conn)?;
166    println!("PI sync...");
167    let pi = seq::sync_pi(&mut conn)?;
168    print_handshake(&dsp, &avr, &pi);
169
170    // 3. Configure
171    let settings = default_avr_settings();
172    if let Some(ref cal) = settings.radar_cal {
173        println!(
174            "Configuring... mode=Outdoor ball=RCT range={}mm height={}mm",
175            cal.range_mm, cal.height_mm,
176        );
177    } else {
178        println!("Configuring... mode=Outdoor ball=RCT (no radar cal)");
179    }
180    seq::configure_avr(&mut conn, &settings)?;
181    seq::configure_camera(&mut conn, &default_cam_config())?;
182
183    // 4. Arm
184    seq::arm(&mut conn)?;
185    println!("=== ARMED — waiting for shots ===");
186
187    // 5. Non-blocking event loop
188    conn.stream_mut()
189        .set_read_timeout(Some(Duration::from_millis(1)))?;
190    let mut last_keepalive = Instant::now();
191    let mut shot: Option<ShotSequencer> = None;
192
193    loop {
194        if let Some(env) = conn.recv()? {
195            // If a shot sequence is active, feed it first.
196            if let Some(ref mut s) = shot {
197                let actions = s.feed(&env);
198                for a in actions {
199                    send_action(&mut conn, a)?;
200                }
201                if s.is_complete() {
202                    let data = shot.take().unwrap().into_result();
203                    print_shot(&data);
204                    continue;
205                }
206            }
207
208            // Handle messages not consumed by the shot sequencer.
209            match &env.message {
210                Message::ShotText(st) if st.is_processed() && shot.is_none() => {
211                    println!("  Shot processed — starting shot sequence...");
212                    let (s, actions) = ShotSequencer::new();
213                    for a in actions {
214                        send_action(&mut conn, a)?;
215                    }
216                    shot = Some(s);
217                }
218
219                // --- Flight result (primary) ---
220                Message::FlightResult(r) => {
221                    println!("  Flight result (shot #{}):", r.total);
222                    println!(
223                        "    Ball speed: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
224                        ms_to_mph(r.launch_speed),
225                        r.launch_elevation,
226                        r.launch_azimuth,
227                    );
228                    println!(
229                        "    Carry: {:.1} yd  Total: {:.1} yd  Height: {:.1} ft",
230                        m_to_yd(r.carry_distance),
231                        m_to_yd(r.total_distance),
232                        m_to_ft(r.max_height),
233                    );
234                    println!(
235                        "    Backspin: {} rpm  Sidespin: {} rpm",
236                        r.backspin_rpm, r.sidespin_rpm,
237                    );
238                    println!(
239                        "    Club speed: {:.1} mph  Path: {:.1}°  AoA: {:.1}°",
240                        ms_to_mph(r.clubhead_speed),
241                        r.club_strike_direction,
242                        r.club_attack_angle,
243                    );
244                    println!(
245                        "    Face: {:.1}°  Loft: {:.1}°",
246                        r.club_face_angle, r.club_effective_loft,
247                    );
248                }
249
250                // --- Flight result v1 (early) ---
251                Message::FlightResultV1(r) => {
252                    println!("  Flight result v1 (shot #{}):", r.total);
253                    println!(
254                        "    Ball: {:.1} mph  Club: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
255                        ms_to_mph(r.ball_velocity),
256                        ms_to_mph(r.club_velocity),
257                        r.elevation,
258                        r.azimuth,
259                    );
260                    println!(
261                        "    Dist: {:.1} yd  Height: {:.1} ft  Backspin: {} rpm",
262                        m_to_yd(r.distance),
263                        m_to_ft(r.height),
264                        r.backspin_rpm,
265                    );
266                }
267
268                // --- Club result ---
269                Message::ClubResult(r) => {
270                    println!("  Club result:");
271                    println!(
272                        "    Club speed: {:.1} mph (pre) / {:.1} mph (post)  Smash: {:.2}",
273                        ms_to_mph(r.pre_club_speed),
274                        ms_to_mph(r.post_club_speed),
275                        r.smash_factor,
276                    );
277                    println!(
278                        "    Path: {:.1}°  Face: {:.1}°  AoA: {:.1}°  Loft: {:.1}°",
279                        r.strike_direction, r.face_angle, r.attack_angle, r.dynamic_loft,
280                    );
281                    println!(
282                        "    Low point: {:.1} in  Club height: {:.1} in",
283                        m_to_in(r.club_offset),
284                        m_to_in(r.club_height),
285                    );
286                }
287
288                // --- Spin result ---
289                Message::SpinResult(r) => {
290                    println!("  Spin result:");
291                    println!(
292                        "    Total spin: {} rpm (PM final)  Axis: {:.1}°  Method: {}",
293                        r.pm_spin_final, r.spin_axis, r.spin_method,
294                    );
295                    println!(
296                        "    AM: {} rpm  PM: {} rpm  Launch: {} rpm  AOD: {} rpm  PLL: {} rpm",
297                        r.am_spin, r.pm_spin, r.launch_spin, r.aod_spin, r.pll_spin,
298                    );
299                }
300
301                // --- Speed profile ---
302                Message::SpeedProfile(r) => {
303                    let peak = r.speeds.iter().copied().fold(0.0_f64, f64::max);
304                    println!(
305                        "  Speed profile: {} samples ({}+{})  peak: {:.1} mph",
306                        r.speeds.len(),
307                        r.num_pre,
308                        r.num_post,
309                        ms_to_mph(peak),
310                    );
311                }
312
313                // --- Tracking status ---
314                Message::TrackingStatus(r) => {
315                    println!(
316                        "  Tracking: iter={} quality={} prc_count={} trigger_idx={}",
317                        r.processing_iteration,
318                        r.result_quality,
319                        r.prc_tracking_count,
320                        r.trigger_idx,
321                    );
322                }
323
324                // --- PRC data ---
325                Message::PrcData(r) => {
326                    println!("  PRC: seq={} points={}", r.sequence, r.points.len());
327                }
328
329                // --- Club PRC data ---
330                Message::ClubPrc(r) => {
331                    println!("  Club PRC: points={}", r.points.len());
332                }
333
334                // --- Camera image available ---
335                Message::CamImageAvail(r) => {
336                    println!(
337                        "  Camera: streaming={} fusion={} video={}",
338                        r.streaming_available, r.fusion_available, r.video_available,
339                    );
340                }
341
342                // --- DspStatus from keepalive ---
343                Message::DspStatus(s) => {
344                    println!(
345                        "[keepalive] battery={}%{}  temp={:.1}°C",
346                        s.battery_percent(),
347                        if s.external_power() {
348                            " (plugged in)"
349                        } else {
350                            ""
351                        },
352                        s.temperature_c(),
353                    );
354                }
355                Message::AvrStatus(s) => {
356                    println!("[keepalive] tilt={:.1}° roll={:.1}°", s.tilt, -s.roll);
357                }
358
359                // --- Config ack / noise ---
360                Message::ConfigAck(_) | Message::PiStatus(_) => {}
361
362                // --- Everything else: debug line is enough ---
363                _ => {}
364            }
365        }
366
367        // Heartbeats always fire — even during shot sequencing.
368        if last_keepalive.elapsed() >= Duration::from_secs(1) {
369            for a in seq::keepalive_actions() {
370                send_action(&mut conn, a)?;
371            }
372            last_keepalive = Instant::now();
373        }
374    }
375}
Source

pub fn set_on_send(&mut self, f: impl FnMut(&Command, BusAddr) + 'static)

Register a callback invoked at the top of every send() call.

Examples found in repository?
examples/client.rs (line 39)
33fn run() -> Result<(), ironsight::ConnError> {
34    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
35    println!("Connecting to {addr}...");
36    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
37    println!("Connected.");
38
39    conn.set_on_send(|cmd, dest| println!(">> {}", cmd.debug_hex(dest)));
40    conn.set_on_recv(|env| println!("<< {env:?}"));
41
42    let mut client = BinaryClient::from_tcp(conn)?;
43
44    // Enqueue the full startup sequence.
45    client.handshake();
46    client.configure_avr(default_avr_settings());
47    client.configure_cam(default_cam_config());
48    client.arm();
49
50    println!("Starting poll loop...");
51    loop {
52        if let Some(event) = client.poll()? {
53            match event {
54                BinaryEvent::Handshake(h) => {
55                    println!("\n=== Handshake complete ===");
56                    println!("  SSID: {}  password: {}", h.pi.ssid, h.pi.password);
57                    println!("  DSP: {}", h.dsp.dev_info.text);
58                    println!("  AVR: {}", h.avr.dev_info.text);
59                    println!("  PI:  {}", h.pi.dev_info.text);
60                    println!(
61                        "  Battery: {}%{}",
62                        h.dsp.status.battery_percent(),
63                        if h.dsp.status.external_power() {
64                            " (plugged in)"
65                        } else {
66                            ""
67                        },
68                    );
69                }
70                BinaryEvent::Configured => {
71                    println!("\n=== Configured ===");
72                }
73                BinaryEvent::Armed => {
74                    println!("\n=== ARMED — waiting for shots ===");
75                }
76                BinaryEvent::Trigger => {
77                    println!("\n  ** BALL TRIGGER **");
78                }
79                BinaryEvent::ShotComplete(data) => {
80                    println!("\n  === Shot complete ===");
81                    if let Some(ref f) = data.flight {
82                        println!(
83                            "    Ball: {:.1} mph  Carry: {:.0} yd  VLA: {:.1}°  HLA: {:.1}°",
84                            ms_to_mph(f.launch_speed),
85                            m_to_yd(f.carry_distance),
86                            f.launch_elevation,
87                            f.launch_azimuth,
88                        );
89                    }
90                    if let Some(ref c) = data.club {
91                        println!(
92                            "    Club: {:.1} mph  Smash: {:.2}  Path: {:.1}°  Face: {:.1}°",
93                            ms_to_mph(c.pre_club_speed),
94                            c.smash_factor,
95                            c.strike_direction,
96                            c.face_angle,
97                        );
98                    }
99                    if let Some(ref s) = data.spin {
100                        println!(
101                            "    Spin: {} rpm  Axis: {:.1}°",
102                            s.pm_spin_final, s.spin_axis,
103                        );
104                    }
105                    println!("  === RE-ARMED ===");
106                }
107                BinaryEvent::Disarmed => {
108                    println!("\n=== Disarmed ===");
109                }
110                BinaryEvent::ShotDatum(_)
111                | BinaryEvent::Keepalive(_)
112                | BinaryEvent::Message(_) => {
113                    // on_recv callback already printed it
114                }
115            }
116        }
117    }
118}
More examples
Hide additional examples
examples/mode_change_test.rs (lines 56-58)
47fn run() -> Result<(), ironsight::ConnError> {
48    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
49    println!("=== Mode Change Integration Test ===\n");
50
51    // ── Step 1: Connect ──────────────────────────────────────────────────
52    println!("[connect] connecting to {addr}...");
53    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
54    println!("[connect] OK\n");
55
56    conn.set_on_send(|cmd, dest| {
57        eprintln!("  >> {dest:?} {}", cmd.debug_hex(dest));
58    });
59    conn.set_on_recv(|env| {
60        let hex: String = env.raw.iter().map(|b| format!("{b:02X}")).collect();
61        eprintln!("  << 0x{:02X} {hex} | {:?}", env.type_id, env.message);
62    });
63
64    let mut client = BinaryClient::from_tcp(conn)?;
65    client.set_keepalive_interval(Duration::from_secs(3));
66
67    // Enqueue: handshake → configure(outdoor) → cam → arm
68    client.handshake();
69    client.configure_avr(avr_settings_full(MODE_OUTDOOR));
70    client.configure_cam(cam_config());
71    client.arm();
72
73    let mut step = Step::InitialArm;
74    let mut phase_start = Instant::now();
75    let timeout = Duration::from_secs(60);
76
77    // Track sub-events within InitialArm
78    let mut handshake_done = false;
79    let mut configure_count = 0u8; // need 2: AVR + cam
80
81    println!("[step] InitialArm: handshake → configure(outdoor/9) → cam → arm");
82
83    loop {
84        if phase_start.elapsed() > timeout {
85            return Err(ironsight::ConnError::Protocol(format!(
86                "timeout in step {step:?} after {timeout:?}"
87            )));
88        }
89
90        let event = client.poll()?;
91        let Some(event) = event else {
92            continue;
93        };
94
95        match (&step, &event) {
96            // ── InitialArm phase ──────────────────────────────────────
97            (Step::InitialArm, BinaryEvent::Handshake(h)) => {
98                println!(
99                    "  [handshake] OK — {} | battery {}%",
100                    h.avr.dev_info.text.trim(),
101                    h.dsp.status.battery_percent()
102                );
103                handshake_done = true;
104            }
105            (Step::InitialArm, BinaryEvent::Configured) => {
106                configure_count += 1;
107                let label = if configure_count == 1 { "AVR" } else { "cam" };
108                println!("  [configured] {label} OK");
109            }
110            (Step::InitialArm, BinaryEvent::Armed) => {
111                println!("  [armed] OK — device armed in outdoor mode");
112                assert!(handshake_done, "armed before handshake");
113                assert!(configure_count >= 2, "armed before both configures");
114
115                // Transition: wait for keepalive
116                step = Step::WaitKeepalive;
117                phase_start = Instant::now();
118                println!("\n[step] WaitKeepalive: waiting for keepalive...");
119            }
120
121            // ── WaitKeepalive ─────────────────────────────────────────
122            (Step::WaitKeepalive, BinaryEvent::Keepalive(snap)) => {
123                let avr = snap.avr.as_ref().map(|a| format!("tilt={:.1}", a.tilt));
124                let dsp = snap
125                    .dsp
126                    .as_ref()
127                    .map(|d| format!("bat={}%", d.battery_percent()));
128                println!("  [keepalive] OK — {} {}", dsp.unwrap_or_default(), avr.unwrap_or_default());
129
130                // Transition: first mode change outdoor → chipping
131                step = Step::ModeChange1;
132                phase_start = Instant::now();
133                println!("\n[step] ModeChange1: outdoor(9) → chipping(5)");
134                client.configure_avr(avr_settings_mode_only(MODE_CHIPPING));
135                client.arm();
136            }
137
138            // ── ModeChange1 (outdoor → chipping) ─────────────────────
139            (Step::ModeChange1, BinaryEvent::Disarmed) => {
140                println!("  [disarmed] OK");
141            }
142            (Step::ModeChange1, BinaryEvent::Configured) => {
143                println!("  [configured] chipping OK");
144            }
145            (Step::ModeChange1, BinaryEvent::Armed) => {
146                println!("  [armed] OK — chipping mode");
147
148                step = Step::WaitKeepalive2;
149                phase_start = Instant::now();
150                println!("\n[step] WaitKeepalive2: waiting for keepalive...");
151            }
152
153            // ── WaitKeepalive2 ────────────────────────────────────────
154            (Step::WaitKeepalive2, BinaryEvent::Keepalive(snap)) => {
155                let dsp = snap
156                    .dsp
157                    .as_ref()
158                    .map(|d| format!("bat={}%", d.battery_percent()));
159                println!("  [keepalive] OK — {}", dsp.unwrap_or_default());
160
161                // Transition: second mode change chipping → putting
162                step = Step::ModeChange2;
163                phase_start = Instant::now();
164                println!("\n[step] ModeChange2: chipping(5) → putting(3)");
165                client.configure_avr(avr_settings_mode_only(MODE_PUTTING));
166                client.arm();
167            }
168
169            // ── ModeChange2 (chipping → putting) ─────────────────────
170            (Step::ModeChange2, BinaryEvent::Disarmed) => {
171                println!("  [disarmed] OK");
172            }
173            (Step::ModeChange2, BinaryEvent::Configured) => {
174                println!("  [configured] putting OK");
175            }
176            (Step::ModeChange2, BinaryEvent::Armed) => {
177                println!("  [armed] OK — putting mode");
178
179                step = Step::WaitKeepalive3;
180                phase_start = Instant::now();
181                println!("\n[step] WaitKeepalive3: waiting for keepalive...");
182            }
183
184            // ── WaitKeepalive3 ────────────────────────────────────────
185            (Step::WaitKeepalive3, BinaryEvent::Keepalive(snap)) => {
186                let dsp = snap
187                    .dsp
188                    .as_ref()
189                    .map(|d| format!("bat={}%", d.battery_percent()));
190                println!("  [keepalive] OK — {}", dsp.unwrap_or_default());
191
192                step = Step::Done;
193            }
194
195            // ── Done ──────────────────────────────────────────────────
196            (Step::Done, _) => unreachable!(),
197
198            // ── Passthrough (text, messages, etc.) ────────────────────
199            (_, BinaryEvent::Message(_)) => {}
200            (_, BinaryEvent::Keepalive(_)) => {} // extra keepalives
201            (_, other) => {
202                println!("  [unexpected] {other:?} in step {step:?}");
203            }
204        }
205
206        if step == Step::Done {
207            break;
208        }
209    }
210
211    println!("\n=== ALL STEPS PASSED ===");
212    println!("  1. Connect + handshake + configure(outdoor) + arm  ✓");
213    println!("  2. Keepalive received while armed                  ✓");
214    println!("  3. Mode change outdoor → chipping                  ✓");
215    println!("  4. Keepalive after mode change                     ✓");
216    println!("  5. Mode change chipping → putting                  ✓");
217    println!("  6. Keepalive after second mode change              ✓");
218
219    Ok(())
220}
examples/event_loop.rs (line 158)
150fn run() -> Result<(), ConnError> {
151    // 1. Connect
152    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
153    println!("Connecting to {addr}...");
154    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
155    println!("Connected.");
156
157    // Wire-level trace callbacks.
158    conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
159    conn.set_on_recv(|env| println!("\n<< {env:?}"));
160
161    // 2. Handshake (blocking via drive())
162    println!("DSP sync...");
163    let dsp = seq::sync_dsp(&mut conn)?;
164    println!("AVR sync...");
165    let avr = seq::sync_avr(&mut conn)?;
166    println!("PI sync...");
167    let pi = seq::sync_pi(&mut conn)?;
168    print_handshake(&dsp, &avr, &pi);
169
170    // 3. Configure
171    let settings = default_avr_settings();
172    if let Some(ref cal) = settings.radar_cal {
173        println!(
174            "Configuring... mode=Outdoor ball=RCT range={}mm height={}mm",
175            cal.range_mm, cal.height_mm,
176        );
177    } else {
178        println!("Configuring... mode=Outdoor ball=RCT (no radar cal)");
179    }
180    seq::configure_avr(&mut conn, &settings)?;
181    seq::configure_camera(&mut conn, &default_cam_config())?;
182
183    // 4. Arm
184    seq::arm(&mut conn)?;
185    println!("=== ARMED — waiting for shots ===");
186
187    // 5. Non-blocking event loop
188    conn.stream_mut()
189        .set_read_timeout(Some(Duration::from_millis(1)))?;
190    let mut last_keepalive = Instant::now();
191    let mut shot: Option<ShotSequencer> = None;
192
193    loop {
194        if let Some(env) = conn.recv()? {
195            // If a shot sequence is active, feed it first.
196            if let Some(ref mut s) = shot {
197                let actions = s.feed(&env);
198                for a in actions {
199                    send_action(&mut conn, a)?;
200                }
201                if s.is_complete() {
202                    let data = shot.take().unwrap().into_result();
203                    print_shot(&data);
204                    continue;
205                }
206            }
207
208            // Handle messages not consumed by the shot sequencer.
209            match &env.message {
210                Message::ShotText(st) if st.is_processed() && shot.is_none() => {
211                    println!("  Shot processed — starting shot sequence...");
212                    let (s, actions) = ShotSequencer::new();
213                    for a in actions {
214                        send_action(&mut conn, a)?;
215                    }
216                    shot = Some(s);
217                }
218
219                // --- Flight result (primary) ---
220                Message::FlightResult(r) => {
221                    println!("  Flight result (shot #{}):", r.total);
222                    println!(
223                        "    Ball speed: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
224                        ms_to_mph(r.launch_speed),
225                        r.launch_elevation,
226                        r.launch_azimuth,
227                    );
228                    println!(
229                        "    Carry: {:.1} yd  Total: {:.1} yd  Height: {:.1} ft",
230                        m_to_yd(r.carry_distance),
231                        m_to_yd(r.total_distance),
232                        m_to_ft(r.max_height),
233                    );
234                    println!(
235                        "    Backspin: {} rpm  Sidespin: {} rpm",
236                        r.backspin_rpm, r.sidespin_rpm,
237                    );
238                    println!(
239                        "    Club speed: {:.1} mph  Path: {:.1}°  AoA: {:.1}°",
240                        ms_to_mph(r.clubhead_speed),
241                        r.club_strike_direction,
242                        r.club_attack_angle,
243                    );
244                    println!(
245                        "    Face: {:.1}°  Loft: {:.1}°",
246                        r.club_face_angle, r.club_effective_loft,
247                    );
248                }
249
250                // --- Flight result v1 (early) ---
251                Message::FlightResultV1(r) => {
252                    println!("  Flight result v1 (shot #{}):", r.total);
253                    println!(
254                        "    Ball: {:.1} mph  Club: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
255                        ms_to_mph(r.ball_velocity),
256                        ms_to_mph(r.club_velocity),
257                        r.elevation,
258                        r.azimuth,
259                    );
260                    println!(
261                        "    Dist: {:.1} yd  Height: {:.1} ft  Backspin: {} rpm",
262                        m_to_yd(r.distance),
263                        m_to_ft(r.height),
264                        r.backspin_rpm,
265                    );
266                }
267
268                // --- Club result ---
269                Message::ClubResult(r) => {
270                    println!("  Club result:");
271                    println!(
272                        "    Club speed: {:.1} mph (pre) / {:.1} mph (post)  Smash: {:.2}",
273                        ms_to_mph(r.pre_club_speed),
274                        ms_to_mph(r.post_club_speed),
275                        r.smash_factor,
276                    );
277                    println!(
278                        "    Path: {:.1}°  Face: {:.1}°  AoA: {:.1}°  Loft: {:.1}°",
279                        r.strike_direction, r.face_angle, r.attack_angle, r.dynamic_loft,
280                    );
281                    println!(
282                        "    Low point: {:.1} in  Club height: {:.1} in",
283                        m_to_in(r.club_offset),
284                        m_to_in(r.club_height),
285                    );
286                }
287
288                // --- Spin result ---
289                Message::SpinResult(r) => {
290                    println!("  Spin result:");
291                    println!(
292                        "    Total spin: {} rpm (PM final)  Axis: {:.1}°  Method: {}",
293                        r.pm_spin_final, r.spin_axis, r.spin_method,
294                    );
295                    println!(
296                        "    AM: {} rpm  PM: {} rpm  Launch: {} rpm  AOD: {} rpm  PLL: {} rpm",
297                        r.am_spin, r.pm_spin, r.launch_spin, r.aod_spin, r.pll_spin,
298                    );
299                }
300
301                // --- Speed profile ---
302                Message::SpeedProfile(r) => {
303                    let peak = r.speeds.iter().copied().fold(0.0_f64, f64::max);
304                    println!(
305                        "  Speed profile: {} samples ({}+{})  peak: {:.1} mph",
306                        r.speeds.len(),
307                        r.num_pre,
308                        r.num_post,
309                        ms_to_mph(peak),
310                    );
311                }
312
313                // --- Tracking status ---
314                Message::TrackingStatus(r) => {
315                    println!(
316                        "  Tracking: iter={} quality={} prc_count={} trigger_idx={}",
317                        r.processing_iteration,
318                        r.result_quality,
319                        r.prc_tracking_count,
320                        r.trigger_idx,
321                    );
322                }
323
324                // --- PRC data ---
325                Message::PrcData(r) => {
326                    println!("  PRC: seq={} points={}", r.sequence, r.points.len());
327                }
328
329                // --- Club PRC data ---
330                Message::ClubPrc(r) => {
331                    println!("  Club PRC: points={}", r.points.len());
332                }
333
334                // --- Camera image available ---
335                Message::CamImageAvail(r) => {
336                    println!(
337                        "  Camera: streaming={} fusion={} video={}",
338                        r.streaming_available, r.fusion_available, r.video_available,
339                    );
340                }
341
342                // --- DspStatus from keepalive ---
343                Message::DspStatus(s) => {
344                    println!(
345                        "[keepalive] battery={}%{}  temp={:.1}°C",
346                        s.battery_percent(),
347                        if s.external_power() {
348                            " (plugged in)"
349                        } else {
350                            ""
351                        },
352                        s.temperature_c(),
353                    );
354                }
355                Message::AvrStatus(s) => {
356                    println!("[keepalive] tilt={:.1}° roll={:.1}°", s.tilt, -s.roll);
357                }
358
359                // --- Config ack / noise ---
360                Message::ConfigAck(_) | Message::PiStatus(_) => {}
361
362                // --- Everything else: debug line is enough ---
363                _ => {}
364            }
365        }
366
367        // Heartbeats always fire — even during shot sequencing.
368        if last_keepalive.elapsed() >= Duration::from_secs(1) {
369            for a in seq::keepalive_actions() {
370                send_action(&mut conn, a)?;
371            }
372            last_keepalive = Instant::now();
373        }
374    }
375}
Source

pub fn set_on_recv(&mut self, f: impl FnMut(&Envelope) + 'static)

Register a callback invoked after every successful frame decode.

Examples found in repository?
examples/client.rs (line 40)
33fn run() -> Result<(), ironsight::ConnError> {
34    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
35    println!("Connecting to {addr}...");
36    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
37    println!("Connected.");
38
39    conn.set_on_send(|cmd, dest| println!(">> {}", cmd.debug_hex(dest)));
40    conn.set_on_recv(|env| println!("<< {env:?}"));
41
42    let mut client = BinaryClient::from_tcp(conn)?;
43
44    // Enqueue the full startup sequence.
45    client.handshake();
46    client.configure_avr(default_avr_settings());
47    client.configure_cam(default_cam_config());
48    client.arm();
49
50    println!("Starting poll loop...");
51    loop {
52        if let Some(event) = client.poll()? {
53            match event {
54                BinaryEvent::Handshake(h) => {
55                    println!("\n=== Handshake complete ===");
56                    println!("  SSID: {}  password: {}", h.pi.ssid, h.pi.password);
57                    println!("  DSP: {}", h.dsp.dev_info.text);
58                    println!("  AVR: {}", h.avr.dev_info.text);
59                    println!("  PI:  {}", h.pi.dev_info.text);
60                    println!(
61                        "  Battery: {}%{}",
62                        h.dsp.status.battery_percent(),
63                        if h.dsp.status.external_power() {
64                            " (plugged in)"
65                        } else {
66                            ""
67                        },
68                    );
69                }
70                BinaryEvent::Configured => {
71                    println!("\n=== Configured ===");
72                }
73                BinaryEvent::Armed => {
74                    println!("\n=== ARMED — waiting for shots ===");
75                }
76                BinaryEvent::Trigger => {
77                    println!("\n  ** BALL TRIGGER **");
78                }
79                BinaryEvent::ShotComplete(data) => {
80                    println!("\n  === Shot complete ===");
81                    if let Some(ref f) = data.flight {
82                        println!(
83                            "    Ball: {:.1} mph  Carry: {:.0} yd  VLA: {:.1}°  HLA: {:.1}°",
84                            ms_to_mph(f.launch_speed),
85                            m_to_yd(f.carry_distance),
86                            f.launch_elevation,
87                            f.launch_azimuth,
88                        );
89                    }
90                    if let Some(ref c) = data.club {
91                        println!(
92                            "    Club: {:.1} mph  Smash: {:.2}  Path: {:.1}°  Face: {:.1}°",
93                            ms_to_mph(c.pre_club_speed),
94                            c.smash_factor,
95                            c.strike_direction,
96                            c.face_angle,
97                        );
98                    }
99                    if let Some(ref s) = data.spin {
100                        println!(
101                            "    Spin: {} rpm  Axis: {:.1}°",
102                            s.pm_spin_final, s.spin_axis,
103                        );
104                    }
105                    println!("  === RE-ARMED ===");
106                }
107                BinaryEvent::Disarmed => {
108                    println!("\n=== Disarmed ===");
109                }
110                BinaryEvent::ShotDatum(_)
111                | BinaryEvent::Keepalive(_)
112                | BinaryEvent::Message(_) => {
113                    // on_recv callback already printed it
114                }
115            }
116        }
117    }
118}
More examples
Hide additional examples
examples/mode_change_test.rs (lines 59-62)
47fn run() -> Result<(), ironsight::ConnError> {
48    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
49    println!("=== Mode Change Integration Test ===\n");
50
51    // ── Step 1: Connect ──────────────────────────────────────────────────
52    println!("[connect] connecting to {addr}...");
53    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
54    println!("[connect] OK\n");
55
56    conn.set_on_send(|cmd, dest| {
57        eprintln!("  >> {dest:?} {}", cmd.debug_hex(dest));
58    });
59    conn.set_on_recv(|env| {
60        let hex: String = env.raw.iter().map(|b| format!("{b:02X}")).collect();
61        eprintln!("  << 0x{:02X} {hex} | {:?}", env.type_id, env.message);
62    });
63
64    let mut client = BinaryClient::from_tcp(conn)?;
65    client.set_keepalive_interval(Duration::from_secs(3));
66
67    // Enqueue: handshake → configure(outdoor) → cam → arm
68    client.handshake();
69    client.configure_avr(avr_settings_full(MODE_OUTDOOR));
70    client.configure_cam(cam_config());
71    client.arm();
72
73    let mut step = Step::InitialArm;
74    let mut phase_start = Instant::now();
75    let timeout = Duration::from_secs(60);
76
77    // Track sub-events within InitialArm
78    let mut handshake_done = false;
79    let mut configure_count = 0u8; // need 2: AVR + cam
80
81    println!("[step] InitialArm: handshake → configure(outdoor/9) → cam → arm");
82
83    loop {
84        if phase_start.elapsed() > timeout {
85            return Err(ironsight::ConnError::Protocol(format!(
86                "timeout in step {step:?} after {timeout:?}"
87            )));
88        }
89
90        let event = client.poll()?;
91        let Some(event) = event else {
92            continue;
93        };
94
95        match (&step, &event) {
96            // ── InitialArm phase ──────────────────────────────────────
97            (Step::InitialArm, BinaryEvent::Handshake(h)) => {
98                println!(
99                    "  [handshake] OK — {} | battery {}%",
100                    h.avr.dev_info.text.trim(),
101                    h.dsp.status.battery_percent()
102                );
103                handshake_done = true;
104            }
105            (Step::InitialArm, BinaryEvent::Configured) => {
106                configure_count += 1;
107                let label = if configure_count == 1 { "AVR" } else { "cam" };
108                println!("  [configured] {label} OK");
109            }
110            (Step::InitialArm, BinaryEvent::Armed) => {
111                println!("  [armed] OK — device armed in outdoor mode");
112                assert!(handshake_done, "armed before handshake");
113                assert!(configure_count >= 2, "armed before both configures");
114
115                // Transition: wait for keepalive
116                step = Step::WaitKeepalive;
117                phase_start = Instant::now();
118                println!("\n[step] WaitKeepalive: waiting for keepalive...");
119            }
120
121            // ── WaitKeepalive ─────────────────────────────────────────
122            (Step::WaitKeepalive, BinaryEvent::Keepalive(snap)) => {
123                let avr = snap.avr.as_ref().map(|a| format!("tilt={:.1}", a.tilt));
124                let dsp = snap
125                    .dsp
126                    .as_ref()
127                    .map(|d| format!("bat={}%", d.battery_percent()));
128                println!("  [keepalive] OK — {} {}", dsp.unwrap_or_default(), avr.unwrap_or_default());
129
130                // Transition: first mode change outdoor → chipping
131                step = Step::ModeChange1;
132                phase_start = Instant::now();
133                println!("\n[step] ModeChange1: outdoor(9) → chipping(5)");
134                client.configure_avr(avr_settings_mode_only(MODE_CHIPPING));
135                client.arm();
136            }
137
138            // ── ModeChange1 (outdoor → chipping) ─────────────────────
139            (Step::ModeChange1, BinaryEvent::Disarmed) => {
140                println!("  [disarmed] OK");
141            }
142            (Step::ModeChange1, BinaryEvent::Configured) => {
143                println!("  [configured] chipping OK");
144            }
145            (Step::ModeChange1, BinaryEvent::Armed) => {
146                println!("  [armed] OK — chipping mode");
147
148                step = Step::WaitKeepalive2;
149                phase_start = Instant::now();
150                println!("\n[step] WaitKeepalive2: waiting for keepalive...");
151            }
152
153            // ── WaitKeepalive2 ────────────────────────────────────────
154            (Step::WaitKeepalive2, BinaryEvent::Keepalive(snap)) => {
155                let dsp = snap
156                    .dsp
157                    .as_ref()
158                    .map(|d| format!("bat={}%", d.battery_percent()));
159                println!("  [keepalive] OK — {}", dsp.unwrap_or_default());
160
161                // Transition: second mode change chipping → putting
162                step = Step::ModeChange2;
163                phase_start = Instant::now();
164                println!("\n[step] ModeChange2: chipping(5) → putting(3)");
165                client.configure_avr(avr_settings_mode_only(MODE_PUTTING));
166                client.arm();
167            }
168
169            // ── ModeChange2 (chipping → putting) ─────────────────────
170            (Step::ModeChange2, BinaryEvent::Disarmed) => {
171                println!("  [disarmed] OK");
172            }
173            (Step::ModeChange2, BinaryEvent::Configured) => {
174                println!("  [configured] putting OK");
175            }
176            (Step::ModeChange2, BinaryEvent::Armed) => {
177                println!("  [armed] OK — putting mode");
178
179                step = Step::WaitKeepalive3;
180                phase_start = Instant::now();
181                println!("\n[step] WaitKeepalive3: waiting for keepalive...");
182            }
183
184            // ── WaitKeepalive3 ────────────────────────────────────────
185            (Step::WaitKeepalive3, BinaryEvent::Keepalive(snap)) => {
186                let dsp = snap
187                    .dsp
188                    .as_ref()
189                    .map(|d| format!("bat={}%", d.battery_percent()));
190                println!("  [keepalive] OK — {}", dsp.unwrap_or_default());
191
192                step = Step::Done;
193            }
194
195            // ── Done ──────────────────────────────────────────────────
196            (Step::Done, _) => unreachable!(),
197
198            // ── Passthrough (text, messages, etc.) ────────────────────
199            (_, BinaryEvent::Message(_)) => {}
200            (_, BinaryEvent::Keepalive(_)) => {} // extra keepalives
201            (_, other) => {
202                println!("  [unexpected] {other:?} in step {step:?}");
203            }
204        }
205
206        if step == Step::Done {
207            break;
208        }
209    }
210
211    println!("\n=== ALL STEPS PASSED ===");
212    println!("  1. Connect + handshake + configure(outdoor) + arm  ✓");
213    println!("  2. Keepalive received while armed                  ✓");
214    println!("  3. Mode change outdoor → chipping                  ✓");
215    println!("  4. Keepalive after mode change                     ✓");
216    println!("  5. Mode change chipping → putting                  ✓");
217    println!("  6. Keepalive after second mode change              ✓");
218
219    Ok(())
220}
examples/event_loop.rs (line 159)
150fn run() -> Result<(), ConnError> {
151    // 1. Connect
152    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
153    println!("Connecting to {addr}...");
154    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
155    println!("Connected.");
156
157    // Wire-level trace callbacks.
158    conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
159    conn.set_on_recv(|env| println!("\n<< {env:?}"));
160
161    // 2. Handshake (blocking via drive())
162    println!("DSP sync...");
163    let dsp = seq::sync_dsp(&mut conn)?;
164    println!("AVR sync...");
165    let avr = seq::sync_avr(&mut conn)?;
166    println!("PI sync...");
167    let pi = seq::sync_pi(&mut conn)?;
168    print_handshake(&dsp, &avr, &pi);
169
170    // 3. Configure
171    let settings = default_avr_settings();
172    if let Some(ref cal) = settings.radar_cal {
173        println!(
174            "Configuring... mode=Outdoor ball=RCT range={}mm height={}mm",
175            cal.range_mm, cal.height_mm,
176        );
177    } else {
178        println!("Configuring... mode=Outdoor ball=RCT (no radar cal)");
179    }
180    seq::configure_avr(&mut conn, &settings)?;
181    seq::configure_camera(&mut conn, &default_cam_config())?;
182
183    // 4. Arm
184    seq::arm(&mut conn)?;
185    println!("=== ARMED — waiting for shots ===");
186
187    // 5. Non-blocking event loop
188    conn.stream_mut()
189        .set_read_timeout(Some(Duration::from_millis(1)))?;
190    let mut last_keepalive = Instant::now();
191    let mut shot: Option<ShotSequencer> = None;
192
193    loop {
194        if let Some(env) = conn.recv()? {
195            // If a shot sequence is active, feed it first.
196            if let Some(ref mut s) = shot {
197                let actions = s.feed(&env);
198                for a in actions {
199                    send_action(&mut conn, a)?;
200                }
201                if s.is_complete() {
202                    let data = shot.take().unwrap().into_result();
203                    print_shot(&data);
204                    continue;
205                }
206            }
207
208            // Handle messages not consumed by the shot sequencer.
209            match &env.message {
210                Message::ShotText(st) if st.is_processed() && shot.is_none() => {
211                    println!("  Shot processed — starting shot sequence...");
212                    let (s, actions) = ShotSequencer::new();
213                    for a in actions {
214                        send_action(&mut conn, a)?;
215                    }
216                    shot = Some(s);
217                }
218
219                // --- Flight result (primary) ---
220                Message::FlightResult(r) => {
221                    println!("  Flight result (shot #{}):", r.total);
222                    println!(
223                        "    Ball speed: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
224                        ms_to_mph(r.launch_speed),
225                        r.launch_elevation,
226                        r.launch_azimuth,
227                    );
228                    println!(
229                        "    Carry: {:.1} yd  Total: {:.1} yd  Height: {:.1} ft",
230                        m_to_yd(r.carry_distance),
231                        m_to_yd(r.total_distance),
232                        m_to_ft(r.max_height),
233                    );
234                    println!(
235                        "    Backspin: {} rpm  Sidespin: {} rpm",
236                        r.backspin_rpm, r.sidespin_rpm,
237                    );
238                    println!(
239                        "    Club speed: {:.1} mph  Path: {:.1}°  AoA: {:.1}°",
240                        ms_to_mph(r.clubhead_speed),
241                        r.club_strike_direction,
242                        r.club_attack_angle,
243                    );
244                    println!(
245                        "    Face: {:.1}°  Loft: {:.1}°",
246                        r.club_face_angle, r.club_effective_loft,
247                    );
248                }
249
250                // --- Flight result v1 (early) ---
251                Message::FlightResultV1(r) => {
252                    println!("  Flight result v1 (shot #{}):", r.total);
253                    println!(
254                        "    Ball: {:.1} mph  Club: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
255                        ms_to_mph(r.ball_velocity),
256                        ms_to_mph(r.club_velocity),
257                        r.elevation,
258                        r.azimuth,
259                    );
260                    println!(
261                        "    Dist: {:.1} yd  Height: {:.1} ft  Backspin: {} rpm",
262                        m_to_yd(r.distance),
263                        m_to_ft(r.height),
264                        r.backspin_rpm,
265                    );
266                }
267
268                // --- Club result ---
269                Message::ClubResult(r) => {
270                    println!("  Club result:");
271                    println!(
272                        "    Club speed: {:.1} mph (pre) / {:.1} mph (post)  Smash: {:.2}",
273                        ms_to_mph(r.pre_club_speed),
274                        ms_to_mph(r.post_club_speed),
275                        r.smash_factor,
276                    );
277                    println!(
278                        "    Path: {:.1}°  Face: {:.1}°  AoA: {:.1}°  Loft: {:.1}°",
279                        r.strike_direction, r.face_angle, r.attack_angle, r.dynamic_loft,
280                    );
281                    println!(
282                        "    Low point: {:.1} in  Club height: {:.1} in",
283                        m_to_in(r.club_offset),
284                        m_to_in(r.club_height),
285                    );
286                }
287
288                // --- Spin result ---
289                Message::SpinResult(r) => {
290                    println!("  Spin result:");
291                    println!(
292                        "    Total spin: {} rpm (PM final)  Axis: {:.1}°  Method: {}",
293                        r.pm_spin_final, r.spin_axis, r.spin_method,
294                    );
295                    println!(
296                        "    AM: {} rpm  PM: {} rpm  Launch: {} rpm  AOD: {} rpm  PLL: {} rpm",
297                        r.am_spin, r.pm_spin, r.launch_spin, r.aod_spin, r.pll_spin,
298                    );
299                }
300
301                // --- Speed profile ---
302                Message::SpeedProfile(r) => {
303                    let peak = r.speeds.iter().copied().fold(0.0_f64, f64::max);
304                    println!(
305                        "  Speed profile: {} samples ({}+{})  peak: {:.1} mph",
306                        r.speeds.len(),
307                        r.num_pre,
308                        r.num_post,
309                        ms_to_mph(peak),
310                    );
311                }
312
313                // --- Tracking status ---
314                Message::TrackingStatus(r) => {
315                    println!(
316                        "  Tracking: iter={} quality={} prc_count={} trigger_idx={}",
317                        r.processing_iteration,
318                        r.result_quality,
319                        r.prc_tracking_count,
320                        r.trigger_idx,
321                    );
322                }
323
324                // --- PRC data ---
325                Message::PrcData(r) => {
326                    println!("  PRC: seq={} points={}", r.sequence, r.points.len());
327                }
328
329                // --- Club PRC data ---
330                Message::ClubPrc(r) => {
331                    println!("  Club PRC: points={}", r.points.len());
332                }
333
334                // --- Camera image available ---
335                Message::CamImageAvail(r) => {
336                    println!(
337                        "  Camera: streaming={} fusion={} video={}",
338                        r.streaming_available, r.fusion_available, r.video_available,
339                    );
340                }
341
342                // --- DspStatus from keepalive ---
343                Message::DspStatus(s) => {
344                    println!(
345                        "[keepalive] battery={}%{}  temp={:.1}°C",
346                        s.battery_percent(),
347                        if s.external_power() {
348                            " (plugged in)"
349                        } else {
350                            ""
351                        },
352                        s.temperature_c(),
353                    );
354                }
355                Message::AvrStatus(s) => {
356                    println!("[keepalive] tilt={:.1}° roll={:.1}°", s.tilt, -s.roll);
357                }
358
359                // --- Config ack / noise ---
360                Message::ConfigAck(_) | Message::PiStatus(_) => {}
361
362                // --- Everything else: debug line is enough ---
363                _ => {}
364            }
365        }
366
367        // Heartbeats always fire — even during shot sequencing.
368        if last_keepalive.elapsed() >= Duration::from_secs(1) {
369            for a in seq::keepalive_actions() {
370                send_action(&mut conn, a)?;
371            }
372            last_keepalive = Instant::now();
373        }
374    }
375}
Source

pub fn send(&mut self, cmd: &Command, dest: BusAddr) -> Result<(), ConnError>

Send a command to the given bus address.

Source

pub fn send_raw(&mut self, frame: &RawFrame) -> Result<(), ConnError>

Send a pre-built raw frame (for messages not in Command, e.g. ClubPrc::encode_request()).

Source

pub fn recv(&mut self) -> Result<Option<Envelope>, ConnError>

Try to receive the next complete frame.

  • Ok(Some(env)) — decoded message.
  • Ok(None) — no data available (WouldBlock/TimedOut from stream).
  • Err(Disconnected) — stream closed by peer.
  • Err(Wire|Io) — decode or I/O error.

The stream’s read timeout controls whether this blocks or returns Ok(None) immediately. Set the timeout on the stream before calling.

Examples found in repository?
examples/event_loop.rs (line 194)
150fn run() -> Result<(), ConnError> {
151    // 1. Connect
152    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
153    println!("Connecting to {addr}...");
154    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
155    println!("Connected.");
156
157    // Wire-level trace callbacks.
158    conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
159    conn.set_on_recv(|env| println!("\n<< {env:?}"));
160
161    // 2. Handshake (blocking via drive())
162    println!("DSP sync...");
163    let dsp = seq::sync_dsp(&mut conn)?;
164    println!("AVR sync...");
165    let avr = seq::sync_avr(&mut conn)?;
166    println!("PI sync...");
167    let pi = seq::sync_pi(&mut conn)?;
168    print_handshake(&dsp, &avr, &pi);
169
170    // 3. Configure
171    let settings = default_avr_settings();
172    if let Some(ref cal) = settings.radar_cal {
173        println!(
174            "Configuring... mode=Outdoor ball=RCT range={}mm height={}mm",
175            cal.range_mm, cal.height_mm,
176        );
177    } else {
178        println!("Configuring... mode=Outdoor ball=RCT (no radar cal)");
179    }
180    seq::configure_avr(&mut conn, &settings)?;
181    seq::configure_camera(&mut conn, &default_cam_config())?;
182
183    // 4. Arm
184    seq::arm(&mut conn)?;
185    println!("=== ARMED — waiting for shots ===");
186
187    // 5. Non-blocking event loop
188    conn.stream_mut()
189        .set_read_timeout(Some(Duration::from_millis(1)))?;
190    let mut last_keepalive = Instant::now();
191    let mut shot: Option<ShotSequencer> = None;
192
193    loop {
194        if let Some(env) = conn.recv()? {
195            // If a shot sequence is active, feed it first.
196            if let Some(ref mut s) = shot {
197                let actions = s.feed(&env);
198                for a in actions {
199                    send_action(&mut conn, a)?;
200                }
201                if s.is_complete() {
202                    let data = shot.take().unwrap().into_result();
203                    print_shot(&data);
204                    continue;
205                }
206            }
207
208            // Handle messages not consumed by the shot sequencer.
209            match &env.message {
210                Message::ShotText(st) if st.is_processed() && shot.is_none() => {
211                    println!("  Shot processed — starting shot sequence...");
212                    let (s, actions) = ShotSequencer::new();
213                    for a in actions {
214                        send_action(&mut conn, a)?;
215                    }
216                    shot = Some(s);
217                }
218
219                // --- Flight result (primary) ---
220                Message::FlightResult(r) => {
221                    println!("  Flight result (shot #{}):", r.total);
222                    println!(
223                        "    Ball speed: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
224                        ms_to_mph(r.launch_speed),
225                        r.launch_elevation,
226                        r.launch_azimuth,
227                    );
228                    println!(
229                        "    Carry: {:.1} yd  Total: {:.1} yd  Height: {:.1} ft",
230                        m_to_yd(r.carry_distance),
231                        m_to_yd(r.total_distance),
232                        m_to_ft(r.max_height),
233                    );
234                    println!(
235                        "    Backspin: {} rpm  Sidespin: {} rpm",
236                        r.backspin_rpm, r.sidespin_rpm,
237                    );
238                    println!(
239                        "    Club speed: {:.1} mph  Path: {:.1}°  AoA: {:.1}°",
240                        ms_to_mph(r.clubhead_speed),
241                        r.club_strike_direction,
242                        r.club_attack_angle,
243                    );
244                    println!(
245                        "    Face: {:.1}°  Loft: {:.1}°",
246                        r.club_face_angle, r.club_effective_loft,
247                    );
248                }
249
250                // --- Flight result v1 (early) ---
251                Message::FlightResultV1(r) => {
252                    println!("  Flight result v1 (shot #{}):", r.total);
253                    println!(
254                        "    Ball: {:.1} mph  Club: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
255                        ms_to_mph(r.ball_velocity),
256                        ms_to_mph(r.club_velocity),
257                        r.elevation,
258                        r.azimuth,
259                    );
260                    println!(
261                        "    Dist: {:.1} yd  Height: {:.1} ft  Backspin: {} rpm",
262                        m_to_yd(r.distance),
263                        m_to_ft(r.height),
264                        r.backspin_rpm,
265                    );
266                }
267
268                // --- Club result ---
269                Message::ClubResult(r) => {
270                    println!("  Club result:");
271                    println!(
272                        "    Club speed: {:.1} mph (pre) / {:.1} mph (post)  Smash: {:.2}",
273                        ms_to_mph(r.pre_club_speed),
274                        ms_to_mph(r.post_club_speed),
275                        r.smash_factor,
276                    );
277                    println!(
278                        "    Path: {:.1}°  Face: {:.1}°  AoA: {:.1}°  Loft: {:.1}°",
279                        r.strike_direction, r.face_angle, r.attack_angle, r.dynamic_loft,
280                    );
281                    println!(
282                        "    Low point: {:.1} in  Club height: {:.1} in",
283                        m_to_in(r.club_offset),
284                        m_to_in(r.club_height),
285                    );
286                }
287
288                // --- Spin result ---
289                Message::SpinResult(r) => {
290                    println!("  Spin result:");
291                    println!(
292                        "    Total spin: {} rpm (PM final)  Axis: {:.1}°  Method: {}",
293                        r.pm_spin_final, r.spin_axis, r.spin_method,
294                    );
295                    println!(
296                        "    AM: {} rpm  PM: {} rpm  Launch: {} rpm  AOD: {} rpm  PLL: {} rpm",
297                        r.am_spin, r.pm_spin, r.launch_spin, r.aod_spin, r.pll_spin,
298                    );
299                }
300
301                // --- Speed profile ---
302                Message::SpeedProfile(r) => {
303                    let peak = r.speeds.iter().copied().fold(0.0_f64, f64::max);
304                    println!(
305                        "  Speed profile: {} samples ({}+{})  peak: {:.1} mph",
306                        r.speeds.len(),
307                        r.num_pre,
308                        r.num_post,
309                        ms_to_mph(peak),
310                    );
311                }
312
313                // --- Tracking status ---
314                Message::TrackingStatus(r) => {
315                    println!(
316                        "  Tracking: iter={} quality={} prc_count={} trigger_idx={}",
317                        r.processing_iteration,
318                        r.result_quality,
319                        r.prc_tracking_count,
320                        r.trigger_idx,
321                    );
322                }
323
324                // --- PRC data ---
325                Message::PrcData(r) => {
326                    println!("  PRC: seq={} points={}", r.sequence, r.points.len());
327                }
328
329                // --- Club PRC data ---
330                Message::ClubPrc(r) => {
331                    println!("  Club PRC: points={}", r.points.len());
332                }
333
334                // --- Camera image available ---
335                Message::CamImageAvail(r) => {
336                    println!(
337                        "  Camera: streaming={} fusion={} video={}",
338                        r.streaming_available, r.fusion_available, r.video_available,
339                    );
340                }
341
342                // --- DspStatus from keepalive ---
343                Message::DspStatus(s) => {
344                    println!(
345                        "[keepalive] battery={}%{}  temp={:.1}°C",
346                        s.battery_percent(),
347                        if s.external_power() {
348                            " (plugged in)"
349                        } else {
350                            ""
351                        },
352                        s.temperature_c(),
353                    );
354                }
355                Message::AvrStatus(s) => {
356                    println!("[keepalive] tilt={:.1}° roll={:.1}°", s.tilt, -s.roll);
357                }
358
359                // --- Config ack / noise ---
360                Message::ConfigAck(_) | Message::PiStatus(_) => {}
361
362                // --- Everything else: debug line is enough ---
363                _ => {}
364            }
365        }
366
367        // Heartbeats always fire — even during shot sequencing.
368        if last_keepalive.elapsed() >= Duration::from_secs(1) {
369            for a in seq::keepalive_actions() {
370                send_action(&mut conn, a)?;
371            }
372            last_keepalive = Instant::now();
373        }
374    }
375}
Source§

impl BinaryConnection<TcpStream>

Source

pub fn connect(addr: impl ToSocketAddrs) -> Result<Self, ConnError>

Connect to a Mevo+ device with the system default timeout.

Source

pub fn connect_timeout( addr: &SocketAddr, timeout: Duration, ) -> Result<Self, ConnError>

Connect with an explicit timeout.

Examples found in repository?
examples/client.rs (line 36)
33fn run() -> Result<(), ironsight::ConnError> {
34    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
35    println!("Connecting to {addr}...");
36    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
37    println!("Connected.");
38
39    conn.set_on_send(|cmd, dest| println!(">> {}", cmd.debug_hex(dest)));
40    conn.set_on_recv(|env| println!("<< {env:?}"));
41
42    let mut client = BinaryClient::from_tcp(conn)?;
43
44    // Enqueue the full startup sequence.
45    client.handshake();
46    client.configure_avr(default_avr_settings());
47    client.configure_cam(default_cam_config());
48    client.arm();
49
50    println!("Starting poll loop...");
51    loop {
52        if let Some(event) = client.poll()? {
53            match event {
54                BinaryEvent::Handshake(h) => {
55                    println!("\n=== Handshake complete ===");
56                    println!("  SSID: {}  password: {}", h.pi.ssid, h.pi.password);
57                    println!("  DSP: {}", h.dsp.dev_info.text);
58                    println!("  AVR: {}", h.avr.dev_info.text);
59                    println!("  PI:  {}", h.pi.dev_info.text);
60                    println!(
61                        "  Battery: {}%{}",
62                        h.dsp.status.battery_percent(),
63                        if h.dsp.status.external_power() {
64                            " (plugged in)"
65                        } else {
66                            ""
67                        },
68                    );
69                }
70                BinaryEvent::Configured => {
71                    println!("\n=== Configured ===");
72                }
73                BinaryEvent::Armed => {
74                    println!("\n=== ARMED — waiting for shots ===");
75                }
76                BinaryEvent::Trigger => {
77                    println!("\n  ** BALL TRIGGER **");
78                }
79                BinaryEvent::ShotComplete(data) => {
80                    println!("\n  === Shot complete ===");
81                    if let Some(ref f) = data.flight {
82                        println!(
83                            "    Ball: {:.1} mph  Carry: {:.0} yd  VLA: {:.1}°  HLA: {:.1}°",
84                            ms_to_mph(f.launch_speed),
85                            m_to_yd(f.carry_distance),
86                            f.launch_elevation,
87                            f.launch_azimuth,
88                        );
89                    }
90                    if let Some(ref c) = data.club {
91                        println!(
92                            "    Club: {:.1} mph  Smash: {:.2}  Path: {:.1}°  Face: {:.1}°",
93                            ms_to_mph(c.pre_club_speed),
94                            c.smash_factor,
95                            c.strike_direction,
96                            c.face_angle,
97                        );
98                    }
99                    if let Some(ref s) = data.spin {
100                        println!(
101                            "    Spin: {} rpm  Axis: {:.1}°",
102                            s.pm_spin_final, s.spin_axis,
103                        );
104                    }
105                    println!("  === RE-ARMED ===");
106                }
107                BinaryEvent::Disarmed => {
108                    println!("\n=== Disarmed ===");
109                }
110                BinaryEvent::ShotDatum(_)
111                | BinaryEvent::Keepalive(_)
112                | BinaryEvent::Message(_) => {
113                    // on_recv callback already printed it
114                }
115            }
116        }
117    }
118}
More examples
Hide additional examples
examples/mode_change_test.rs (line 53)
47fn run() -> Result<(), ironsight::ConnError> {
48    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
49    println!("=== Mode Change Integration Test ===\n");
50
51    // ── Step 1: Connect ──────────────────────────────────────────────────
52    println!("[connect] connecting to {addr}...");
53    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
54    println!("[connect] OK\n");
55
56    conn.set_on_send(|cmd, dest| {
57        eprintln!("  >> {dest:?} {}", cmd.debug_hex(dest));
58    });
59    conn.set_on_recv(|env| {
60        let hex: String = env.raw.iter().map(|b| format!("{b:02X}")).collect();
61        eprintln!("  << 0x{:02X} {hex} | {:?}", env.type_id, env.message);
62    });
63
64    let mut client = BinaryClient::from_tcp(conn)?;
65    client.set_keepalive_interval(Duration::from_secs(3));
66
67    // Enqueue: handshake → configure(outdoor) → cam → arm
68    client.handshake();
69    client.configure_avr(avr_settings_full(MODE_OUTDOOR));
70    client.configure_cam(cam_config());
71    client.arm();
72
73    let mut step = Step::InitialArm;
74    let mut phase_start = Instant::now();
75    let timeout = Duration::from_secs(60);
76
77    // Track sub-events within InitialArm
78    let mut handshake_done = false;
79    let mut configure_count = 0u8; // need 2: AVR + cam
80
81    println!("[step] InitialArm: handshake → configure(outdoor/9) → cam → arm");
82
83    loop {
84        if phase_start.elapsed() > timeout {
85            return Err(ironsight::ConnError::Protocol(format!(
86                "timeout in step {step:?} after {timeout:?}"
87            )));
88        }
89
90        let event = client.poll()?;
91        let Some(event) = event else {
92            continue;
93        };
94
95        match (&step, &event) {
96            // ── InitialArm phase ──────────────────────────────────────
97            (Step::InitialArm, BinaryEvent::Handshake(h)) => {
98                println!(
99                    "  [handshake] OK — {} | battery {}%",
100                    h.avr.dev_info.text.trim(),
101                    h.dsp.status.battery_percent()
102                );
103                handshake_done = true;
104            }
105            (Step::InitialArm, BinaryEvent::Configured) => {
106                configure_count += 1;
107                let label = if configure_count == 1 { "AVR" } else { "cam" };
108                println!("  [configured] {label} OK");
109            }
110            (Step::InitialArm, BinaryEvent::Armed) => {
111                println!("  [armed] OK — device armed in outdoor mode");
112                assert!(handshake_done, "armed before handshake");
113                assert!(configure_count >= 2, "armed before both configures");
114
115                // Transition: wait for keepalive
116                step = Step::WaitKeepalive;
117                phase_start = Instant::now();
118                println!("\n[step] WaitKeepalive: waiting for keepalive...");
119            }
120
121            // ── WaitKeepalive ─────────────────────────────────────────
122            (Step::WaitKeepalive, BinaryEvent::Keepalive(snap)) => {
123                let avr = snap.avr.as_ref().map(|a| format!("tilt={:.1}", a.tilt));
124                let dsp = snap
125                    .dsp
126                    .as_ref()
127                    .map(|d| format!("bat={}%", d.battery_percent()));
128                println!("  [keepalive] OK — {} {}", dsp.unwrap_or_default(), avr.unwrap_or_default());
129
130                // Transition: first mode change outdoor → chipping
131                step = Step::ModeChange1;
132                phase_start = Instant::now();
133                println!("\n[step] ModeChange1: outdoor(9) → chipping(5)");
134                client.configure_avr(avr_settings_mode_only(MODE_CHIPPING));
135                client.arm();
136            }
137
138            // ── ModeChange1 (outdoor → chipping) ─────────────────────
139            (Step::ModeChange1, BinaryEvent::Disarmed) => {
140                println!("  [disarmed] OK");
141            }
142            (Step::ModeChange1, BinaryEvent::Configured) => {
143                println!("  [configured] chipping OK");
144            }
145            (Step::ModeChange1, BinaryEvent::Armed) => {
146                println!("  [armed] OK — chipping mode");
147
148                step = Step::WaitKeepalive2;
149                phase_start = Instant::now();
150                println!("\n[step] WaitKeepalive2: waiting for keepalive...");
151            }
152
153            // ── WaitKeepalive2 ────────────────────────────────────────
154            (Step::WaitKeepalive2, BinaryEvent::Keepalive(snap)) => {
155                let dsp = snap
156                    .dsp
157                    .as_ref()
158                    .map(|d| format!("bat={}%", d.battery_percent()));
159                println!("  [keepalive] OK — {}", dsp.unwrap_or_default());
160
161                // Transition: second mode change chipping → putting
162                step = Step::ModeChange2;
163                phase_start = Instant::now();
164                println!("\n[step] ModeChange2: chipping(5) → putting(3)");
165                client.configure_avr(avr_settings_mode_only(MODE_PUTTING));
166                client.arm();
167            }
168
169            // ── ModeChange2 (chipping → putting) ─────────────────────
170            (Step::ModeChange2, BinaryEvent::Disarmed) => {
171                println!("  [disarmed] OK");
172            }
173            (Step::ModeChange2, BinaryEvent::Configured) => {
174                println!("  [configured] putting OK");
175            }
176            (Step::ModeChange2, BinaryEvent::Armed) => {
177                println!("  [armed] OK — putting mode");
178
179                step = Step::WaitKeepalive3;
180                phase_start = Instant::now();
181                println!("\n[step] WaitKeepalive3: waiting for keepalive...");
182            }
183
184            // ── WaitKeepalive3 ────────────────────────────────────────
185            (Step::WaitKeepalive3, BinaryEvent::Keepalive(snap)) => {
186                let dsp = snap
187                    .dsp
188                    .as_ref()
189                    .map(|d| format!("bat={}%", d.battery_percent()));
190                println!("  [keepalive] OK — {}", dsp.unwrap_or_default());
191
192                step = Step::Done;
193            }
194
195            // ── Done ──────────────────────────────────────────────────
196            (Step::Done, _) => unreachable!(),
197
198            // ── Passthrough (text, messages, etc.) ────────────────────
199            (_, BinaryEvent::Message(_)) => {}
200            (_, BinaryEvent::Keepalive(_)) => {} // extra keepalives
201            (_, other) => {
202                println!("  [unexpected] {other:?} in step {step:?}");
203            }
204        }
205
206        if step == Step::Done {
207            break;
208        }
209    }
210
211    println!("\n=== ALL STEPS PASSED ===");
212    println!("  1. Connect + handshake + configure(outdoor) + arm  ✓");
213    println!("  2. Keepalive received while armed                  ✓");
214    println!("  3. Mode change outdoor → chipping                  ✓");
215    println!("  4. Keepalive after mode change                     ✓");
216    println!("  5. Mode change chipping → putting                  ✓");
217    println!("  6. Keepalive after second mode change              ✓");
218
219    Ok(())
220}
examples/event_loop.rs (line 154)
150fn run() -> Result<(), ConnError> {
151    // 1. Connect
152    let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
153    println!("Connecting to {addr}...");
154    let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
155    println!("Connected.");
156
157    // Wire-level trace callbacks.
158    conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
159    conn.set_on_recv(|env| println!("\n<< {env:?}"));
160
161    // 2. Handshake (blocking via drive())
162    println!("DSP sync...");
163    let dsp = seq::sync_dsp(&mut conn)?;
164    println!("AVR sync...");
165    let avr = seq::sync_avr(&mut conn)?;
166    println!("PI sync...");
167    let pi = seq::sync_pi(&mut conn)?;
168    print_handshake(&dsp, &avr, &pi);
169
170    // 3. Configure
171    let settings = default_avr_settings();
172    if let Some(ref cal) = settings.radar_cal {
173        println!(
174            "Configuring... mode=Outdoor ball=RCT range={}mm height={}mm",
175            cal.range_mm, cal.height_mm,
176        );
177    } else {
178        println!("Configuring... mode=Outdoor ball=RCT (no radar cal)");
179    }
180    seq::configure_avr(&mut conn, &settings)?;
181    seq::configure_camera(&mut conn, &default_cam_config())?;
182
183    // 4. Arm
184    seq::arm(&mut conn)?;
185    println!("=== ARMED — waiting for shots ===");
186
187    // 5. Non-blocking event loop
188    conn.stream_mut()
189        .set_read_timeout(Some(Duration::from_millis(1)))?;
190    let mut last_keepalive = Instant::now();
191    let mut shot: Option<ShotSequencer> = None;
192
193    loop {
194        if let Some(env) = conn.recv()? {
195            // If a shot sequence is active, feed it first.
196            if let Some(ref mut s) = shot {
197                let actions = s.feed(&env);
198                for a in actions {
199                    send_action(&mut conn, a)?;
200                }
201                if s.is_complete() {
202                    let data = shot.take().unwrap().into_result();
203                    print_shot(&data);
204                    continue;
205                }
206            }
207
208            // Handle messages not consumed by the shot sequencer.
209            match &env.message {
210                Message::ShotText(st) if st.is_processed() && shot.is_none() => {
211                    println!("  Shot processed — starting shot sequence...");
212                    let (s, actions) = ShotSequencer::new();
213                    for a in actions {
214                        send_action(&mut conn, a)?;
215                    }
216                    shot = Some(s);
217                }
218
219                // --- Flight result (primary) ---
220                Message::FlightResult(r) => {
221                    println!("  Flight result (shot #{}):", r.total);
222                    println!(
223                        "    Ball speed: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
224                        ms_to_mph(r.launch_speed),
225                        r.launch_elevation,
226                        r.launch_azimuth,
227                    );
228                    println!(
229                        "    Carry: {:.1} yd  Total: {:.1} yd  Height: {:.1} ft",
230                        m_to_yd(r.carry_distance),
231                        m_to_yd(r.total_distance),
232                        m_to_ft(r.max_height),
233                    );
234                    println!(
235                        "    Backspin: {} rpm  Sidespin: {} rpm",
236                        r.backspin_rpm, r.sidespin_rpm,
237                    );
238                    println!(
239                        "    Club speed: {:.1} mph  Path: {:.1}°  AoA: {:.1}°",
240                        ms_to_mph(r.clubhead_speed),
241                        r.club_strike_direction,
242                        r.club_attack_angle,
243                    );
244                    println!(
245                        "    Face: {:.1}°  Loft: {:.1}°",
246                        r.club_face_angle, r.club_effective_loft,
247                    );
248                }
249
250                // --- Flight result v1 (early) ---
251                Message::FlightResultV1(r) => {
252                    println!("  Flight result v1 (shot #{}):", r.total);
253                    println!(
254                        "    Ball: {:.1} mph  Club: {:.1} mph  VLA: {:.1}°  HLA: {:.1}°",
255                        ms_to_mph(r.ball_velocity),
256                        ms_to_mph(r.club_velocity),
257                        r.elevation,
258                        r.azimuth,
259                    );
260                    println!(
261                        "    Dist: {:.1} yd  Height: {:.1} ft  Backspin: {} rpm",
262                        m_to_yd(r.distance),
263                        m_to_ft(r.height),
264                        r.backspin_rpm,
265                    );
266                }
267
268                // --- Club result ---
269                Message::ClubResult(r) => {
270                    println!("  Club result:");
271                    println!(
272                        "    Club speed: {:.1} mph (pre) / {:.1} mph (post)  Smash: {:.2}",
273                        ms_to_mph(r.pre_club_speed),
274                        ms_to_mph(r.post_club_speed),
275                        r.smash_factor,
276                    );
277                    println!(
278                        "    Path: {:.1}°  Face: {:.1}°  AoA: {:.1}°  Loft: {:.1}°",
279                        r.strike_direction, r.face_angle, r.attack_angle, r.dynamic_loft,
280                    );
281                    println!(
282                        "    Low point: {:.1} in  Club height: {:.1} in",
283                        m_to_in(r.club_offset),
284                        m_to_in(r.club_height),
285                    );
286                }
287
288                // --- Spin result ---
289                Message::SpinResult(r) => {
290                    println!("  Spin result:");
291                    println!(
292                        "    Total spin: {} rpm (PM final)  Axis: {:.1}°  Method: {}",
293                        r.pm_spin_final, r.spin_axis, r.spin_method,
294                    );
295                    println!(
296                        "    AM: {} rpm  PM: {} rpm  Launch: {} rpm  AOD: {} rpm  PLL: {} rpm",
297                        r.am_spin, r.pm_spin, r.launch_spin, r.aod_spin, r.pll_spin,
298                    );
299                }
300
301                // --- Speed profile ---
302                Message::SpeedProfile(r) => {
303                    let peak = r.speeds.iter().copied().fold(0.0_f64, f64::max);
304                    println!(
305                        "  Speed profile: {} samples ({}+{})  peak: {:.1} mph",
306                        r.speeds.len(),
307                        r.num_pre,
308                        r.num_post,
309                        ms_to_mph(peak),
310                    );
311                }
312
313                // --- Tracking status ---
314                Message::TrackingStatus(r) => {
315                    println!(
316                        "  Tracking: iter={} quality={} prc_count={} trigger_idx={}",
317                        r.processing_iteration,
318                        r.result_quality,
319                        r.prc_tracking_count,
320                        r.trigger_idx,
321                    );
322                }
323
324                // --- PRC data ---
325                Message::PrcData(r) => {
326                    println!("  PRC: seq={} points={}", r.sequence, r.points.len());
327                }
328
329                // --- Club PRC data ---
330                Message::ClubPrc(r) => {
331                    println!("  Club PRC: points={}", r.points.len());
332                }
333
334                // --- Camera image available ---
335                Message::CamImageAvail(r) => {
336                    println!(
337                        "  Camera: streaming={} fusion={} video={}",
338                        r.streaming_available, r.fusion_available, r.video_available,
339                    );
340                }
341
342                // --- DspStatus from keepalive ---
343                Message::DspStatus(s) => {
344                    println!(
345                        "[keepalive] battery={}%{}  temp={:.1}°C",
346                        s.battery_percent(),
347                        if s.external_power() {
348                            " (plugged in)"
349                        } else {
350                            ""
351                        },
352                        s.temperature_c(),
353                    );
354                }
355                Message::AvrStatus(s) => {
356                    println!("[keepalive] tilt={:.1}° roll={:.1}°", s.tilt, -s.roll);
357                }
358
359                // --- Config ack / noise ---
360                Message::ConfigAck(_) | Message::PiStatus(_) => {}
361
362                // --- Everything else: debug line is enough ---
363                _ => {}
364            }
365        }
366
367        // Heartbeats always fire — even during shot sequencing.
368        if last_keepalive.elapsed() >= Duration::from_secs(1) {
369            for a in seq::keepalive_actions() {
370                send_action(&mut conn, a)?;
371            }
372            last_keepalive = Instant::now();
373        }
374    }
375}
Source

pub fn peer_addr(&self) -> Result<SocketAddr, ConnError>

The peer address of the underlying TCP connection.

Source

pub fn shutdown(&self) -> Result<(), ConnError>

Shut down the TCP connection.

Source

pub fn recv_timeout(&mut self, timeout: Duration) -> Result<Envelope, ConnError>

Block up to timeout for a complete frame.

Returns ConnError::Timeout if no complete frame arrives in time.

This is a convenience wrapper for callers that prefer the old timeout-per-call style. Prefer setting the stream read timeout once and using recv() directly.

Auto Trait Implementations§

§

impl<S> Freeze for BinaryConnection<S>
where S: Freeze,

§

impl<S> !RefUnwindSafe for BinaryConnection<S>

§

impl<S> !Send for BinaryConnection<S>

§

impl<S> !Sync for BinaryConnection<S>

§

impl<S> Unpin for BinaryConnection<S>
where S: Unpin,

§

impl<S> UnsafeUnpin for BinaryConnection<S>
where S: UnsafeUnpin,

§

impl<S> !UnwindSafe for BinaryConnection<S>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.