1use std::net::SocketAddr;
8use std::process;
9use std::time::{Duration, Instant};
10
11use ironsight::conn::DEFAULT_ADDR;
12use ironsight::protocol::camera::CamConfig;
13use ironsight::protocol::config::{MODE_OUTDOOR, ParamData, ParamValue, RadarCal};
14use ironsight::seq::{
15 self, Action, AvrSettings, AvrSync, DspSync, PiSync, ShotSequencer,
16};
17use ironsight::{BinaryConnection, ConnError, Message, Sequence};
18
19fn ms_to_mph(ms: f64) -> f64 {
25 ms * 2.23694
26}
27
28fn m_to_yd(m: f64) -> f64 {
30 m * 1.09361
31}
32
33fn m_to_ft(m: f64) -> f64 {
35 m * 3.28084
36}
37
38fn m_to_in(m: f64) -> f64 {
40 m * 39.3701
41}
42
43fn default_avr_settings() -> AvrSettings {
48 AvrSettings {
49 mode: MODE_OUTDOOR,
50 params: vec![
51 ParamValue {
53 param_id: 0x06,
54 value: ParamData::Int24(0),
55 },
56 ParamValue {
58 param_id: 0x0F,
59 value: ParamData::Float40(1.0),
60 },
61 ParamValue {
63 param_id: 0x26,
64 value: ParamData::Float40(0.0381),
65 },
66 ],
67 radar_cal: RadarCal {
68 range_mm: 2743, height_mm: 25, },
71 }
72}
73
74fn default_cam_config() -> CamConfig {
75 CamConfig {
76 dynamic_config: true,
77 resolution_width: 1024,
78 resolution_height: 768,
79 rotation: 0,
80 ev: 0,
81 quality: 80,
82 framerate: 20,
83 streaming_framerate: 1,
84 ringbuffer_pretime_ms: 1000,
85 ringbuffer_posttime_ms: 4000,
86 raw_camera_mode: 0,
87 fusion_camera_mode: false,
88 raw_shutter_speed_max: 0.0,
89 raw_ev_roi_x: -1,
90 raw_ev_roi_y: -1,
91 raw_ev_roi_width: -1,
92 raw_ev_roi_height: -1,
93 raw_x_offset: -1,
94 raw_bin44: false,
95 raw_live_preview_write_interval_ms: -1,
96 raw_y_offset: -1,
97 buffer_sub_sampling_pre_trigger_div: -1,
98 buffer_sub_sampling_post_trigger_div: -1,
99 buffer_sub_sampling_switch_time_offset: -1.0,
100 buffer_sub_sampling_total_buffer_size: -1,
101 buffer_sub_sampling_pre_trigger_buffer_size: -1,
102 }
103}
104
105fn print_handshake(dsp: &DspSync, avr: &AvrSync, pi: &PiSync) {
110 println!("--- Handshake complete ---");
111 println!(
112 " DSP: type=0x{:02X} pcb={}",
113 dsp.hw_info.dsp_type, dsp.hw_info.pcb
114 );
115 println!(" DSP info: {}", dsp.dev_info.text);
116 println!(" AVR info: {}", avr.dev_info.text);
117 println!(
118 " AVR tilt={:.1}° roll={:.1}°",
119 avr.status.tilt, -avr.status.roll
120 );
121 println!(" PI info: {}", pi.dev_info.text);
122 println!(" SSID: {} password: {}", pi.ssid, pi.password);
123 println!(
124 " Battery: {}% {}",
125 dsp.status.battery_percent(),
126 if dsp.status.external_power() {
127 "(plugged in)"
128 } else {
129 ""
130 },
131 );
132}
133
134fn main() {
139 if let Err(e) = run() {
140 eprintln!("error: {e}");
141 process::exit(1);
142 }
143}
144
145fn send_action(conn: &mut BinaryConnection<std::net::TcpStream>, action: Action) -> Result<(), ConnError> {
146 seq::send_action(conn, action)
147}
148
149fn run() -> Result<(), ConnError> {
150 let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
152 println!("Connecting to {addr}...");
153 let mut conn = BinaryConnection::connect_timeout(&addr, Duration::from_secs(5))?;
154 println!("Connected.");
155
156 conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
158 conn.set_on_recv(|env| println!("\n<< {env:?}"));
159
160 println!("DSP sync...");
162 let dsp = seq::sync_dsp(&mut conn)?;
163 println!("AVR sync...");
164 let avr = seq::sync_avr(&mut conn)?;
165 println!("PI sync...");
166 let pi = seq::sync_pi(&mut conn)?;
167 print_handshake(&dsp, &avr, &pi);
168
169 let settings = default_avr_settings();
171 println!(
172 "Configuring... mode=Outdoor ball=RCT range={}mm height={}mm",
173 settings.radar_cal.range_mm, settings.radar_cal.height_mm,
174 );
175 seq::configure_avr(&mut conn, &settings)?;
176 seq::configure_camera(&mut conn, &default_cam_config())?;
177
178 seq::arm(&mut conn)?;
180 println!("=== ARMED — waiting for shots ===");
181
182 conn.stream_mut()
184 .set_read_timeout(Some(Duration::from_millis(1)))?;
185 let mut last_keepalive = Instant::now();
186 let mut shot: Option<ShotSequencer> = None;
187
188 loop {
189 if let Some(env) = conn.recv()? {
190 if let Some(ref mut s) = shot {
192 let actions = s.feed(&env);
193 for a in actions {
194 send_action(&mut conn, a)?;
195 }
196 if s.is_complete() {
197 let data = shot.take().unwrap().into_result();
198 print_shot(&data);
199 continue;
200 }
201 }
202
203 match &env.message {
205 Message::ShotText(st) if st.is_processed() && shot.is_none() => {
206 println!(" Shot processed — starting shot sequence...");
207 let (s, actions) = ShotSequencer::new();
208 for a in actions {
209 send_action(&mut conn, a)?;
210 }
211 shot = Some(s);
212 }
213
214 Message::FlightResult(r) => {
216 println!(" Flight result (shot #{}):", r.total);
217 println!(
218 " Ball speed: {:.1} mph VLA: {:.1}° HLA: {:.1}°",
219 ms_to_mph(r.launch_speed),
220 r.launch_elevation,
221 r.launch_azimuth,
222 );
223 println!(
224 " Carry: {:.1} yd Total: {:.1} yd Height: {:.1} ft",
225 m_to_yd(r.carry_distance),
226 m_to_yd(r.total_distance),
227 m_to_ft(r.max_height),
228 );
229 println!(
230 " Backspin: {} rpm Sidespin: {} rpm",
231 r.backspin_rpm, r.sidespin_rpm,
232 );
233 println!(
234 " Club speed: {:.1} mph Path: {:.1}° AoA: {:.1}°",
235 ms_to_mph(r.clubhead_speed),
236 r.club_strike_direction,
237 r.club_attack_angle,
238 );
239 println!(
240 " Face: {:.1}° Loft: {:.1}°",
241 r.club_face_angle, r.club_effective_loft,
242 );
243 }
244
245 Message::FlightResultV1(r) => {
247 println!(" Flight result v1 (shot #{}):", r.total);
248 println!(
249 " Ball: {:.1} mph Club: {:.1} mph VLA: {:.1}° HLA: {:.1}°",
250 ms_to_mph(r.ball_velocity),
251 ms_to_mph(r.club_velocity),
252 r.elevation,
253 r.azimuth,
254 );
255 println!(
256 " Dist: {:.1} yd Height: {:.1} ft Backspin: {} rpm",
257 m_to_yd(r.distance),
258 m_to_ft(r.height),
259 r.backspin_rpm,
260 );
261 }
262
263 Message::ClubResult(r) => {
265 println!(" Club result:");
266 println!(
267 " Club speed: {:.1} mph (pre) / {:.1} mph (post) Smash: {:.2}",
268 ms_to_mph(r.pre_club_speed),
269 ms_to_mph(r.post_club_speed),
270 r.smash_factor,
271 );
272 println!(
273 " Path: {:.1}° Face: {:.1}° AoA: {:.1}° Loft: {:.1}°",
274 r.strike_direction, r.face_angle, r.attack_angle, r.dynamic_loft,
275 );
276 println!(
277 " Low point: {:.1} in Club height: {:.1} in",
278 m_to_in(r.club_offset),
279 m_to_in(r.club_height),
280 );
281 }
282
283 Message::SpinResult(r) => {
285 println!(" Spin result:");
286 println!(
287 " Total spin: {} rpm (PM final) Axis: {:.1}° Method: {}",
288 r.pm_spin_final, r.spin_axis, r.spin_method,
289 );
290 println!(
291 " AM: {} rpm PM: {} rpm Launch: {} rpm AOD: {} rpm PLL: {} rpm",
292 r.am_spin, r.pm_spin, r.launch_spin, r.aod_spin, r.pll_spin,
293 );
294 }
295
296 Message::SpeedProfile(r) => {
298 let peak = r.speeds.iter().copied().fold(0.0_f64, f64::max);
299 println!(
300 " Speed profile: {} samples ({}+{}) peak: {:.1} mph",
301 r.speeds.len(),
302 r.num_pre,
303 r.num_post,
304 ms_to_mph(peak),
305 );
306 }
307
308 Message::TrackingStatus(r) => {
310 println!(
311 " Tracking: iter={} quality={} prc_count={} trigger_idx={}",
312 r.processing_iteration,
313 r.result_quality,
314 r.prc_tracking_count,
315 r.trigger_idx,
316 );
317 }
318
319 Message::PrcData(r) => {
321 println!(" PRC: seq={} points={}", r.sequence, r.points.len());
322 }
323
324 Message::ClubPrc(r) => {
326 println!(" Club PRC: points={}", r.points.len());
327 }
328
329 Message::CamImageAvail(r) => {
331 println!(
332 " Camera: streaming={} fusion={} video={}",
333 r.streaming_available, r.fusion_available, r.video_available,
334 );
335 }
336
337 Message::DspStatus(s) => {
339 println!(
340 "[keepalive] battery={}%{} temp={:.1}°C",
341 s.battery_percent(),
342 if s.external_power() {
343 " (plugged in)"
344 } else {
345 ""
346 },
347 s.temperature_c(),
348 );
349 }
350 Message::AvrStatus(s) => {
351 println!("[keepalive] tilt={:.1}° roll={:.1}°", s.tilt, -s.roll);
352 }
353
354 Message::ConfigAck(_) | Message::PiStatus(_) => {}
356
357 _ => {}
359 }
360 }
361
362 if last_keepalive.elapsed() >= Duration::from_secs(1) {
364 for a in seq::keepalive_actions() {
365 send_action(&mut conn, a)?;
366 }
367 last_keepalive = Instant::now();
368 }
369 }
370}
371
372fn print_shot(data: &seq::ShotData) {
373 println!("\n === Shot complete ===");
374 if let Some(ref f) = data.flight {
375 println!(
376 " Ball: {:.1} mph Carry: {:.0} yd",
377 ms_to_mph(f.launch_speed),
378 m_to_yd(f.carry_distance),
379 );
380 }
381 if let Some(ref c) = data.club {
382 println!(
383 " Club: {:.1} mph Smash: {:.2}",
384 ms_to_mph(c.pre_club_speed),
385 c.smash_factor,
386 );
387 }
388 println!(" === RE-ARMED ===\n");
389}