Skip to main content

client/
client.rs

1//! Non-blocking client example: connect, handshake, configure, arm, shot loop.
2//!
3//! Usage: cargo run --example client
4//!
5//! Requires: connected to Mevo+ WiFi (SSID = device serial).
6
7use std::net::SocketAddr;
8use std::process;
9use std::time::Duration;
10
11use ironsight::client::{BinaryClient, BinaryEvent};
12use ironsight::conn::DEFAULT_ADDR;
13use ironsight::protocol::camera::CamConfig;
14use ironsight::protocol::config::{ParamData, ParamValue, RadarCal, MODE_CHIPPING};
15use ironsight::seq::AvrSettings;
16use ironsight::BinaryConnection;
17
18fn ms_to_mph(ms: f64) -> f64 {
19    ms * 2.23694
20}
21
22fn m_to_yd(m: f64) -> f64 {
23    m * 1.09361
24}
25
26fn main() {
27    if let Err(e) = run() {
28        eprintln!("error: {e}");
29        process::exit(1);
30    }
31}
32
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}
119
120fn default_avr_settings() -> AvrSettings {
121    AvrSettings {
122        mode: MODE_CHIPPING,
123        params: vec![
124            ParamValue {
125                param_id: 0x06,
126                value: ParamData::Int24(0),
127            },
128            ParamValue {
129                param_id: 0x0F,
130                value: ParamData::Float40(1.0),
131            },
132            ParamValue {
133                param_id: 0x26,
134                value: ParamData::Float40(0.0381),
135            },
136        ],
137        radar_cal: Some(RadarCal {
138            range_mm: 2743,
139            height_mm: 25,
140        }),
141    }
142}
143
144fn default_cam_config() -> CamConfig {
145    CamConfig {
146        dynamic_config: true,
147        resolution_width: 1024,
148        resolution_height: 768,
149        rotation: 0,
150        ev: 0,
151        quality: 80,
152        framerate: 20,
153        streaming_framerate: 1,
154        ringbuffer_pretime_ms: 1000,
155        ringbuffer_posttime_ms: 4000,
156        raw_camera_mode: 0,
157        fusion_camera_mode: false,
158        raw_shutter_speed_max: 0.0,
159        raw_ev_roi_x: -1,
160        raw_ev_roi_y: -1,
161        raw_ev_roi_width: -1,
162        raw_ev_roi_height: -1,
163        raw_x_offset: -1,
164        raw_bin44: false,
165        raw_live_preview_write_interval_ms: -1,
166        raw_y_offset: -1,
167        buffer_sub_sampling_pre_trigger_div: -1,
168        buffer_sub_sampling_post_trigger_div: -1,
169        buffer_sub_sampling_switch_time_offset: -1.0,
170        buffer_sub_sampling_total_buffer_size: -1,
171        buffer_sub_sampling_pre_trigger_buffer_size: -1,
172    }
173}