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::{ParamData, ParamValue, RadarCal, MODE_CHIPPING};
14use ironsight::seq::{self, Action, AvrSettings, AvrSync, DspSync, PiSync, ShotSequencer};
15use ironsight::{BinaryConnection, ConnError, Message, Sequence};
16
17fn ms_to_mph(ms: f64) -> f64 {
23 ms * 2.23694
24}
25
26fn m_to_yd(m: f64) -> f64 {
28 m * 1.09361
29}
30
31fn m_to_ft(m: f64) -> f64 {
33 m * 3.28084
34}
35
36fn m_to_in(m: f64) -> f64 {
38 m * 39.3701
39}
40
41fn default_avr_settings() -> AvrSettings {
46 AvrSettings {
47 mode: MODE_CHIPPING,
48 params: vec![
49 ParamValue {
51 param_id: 0x06,
52 value: ParamData::Int24(0),
53 },
54 ParamValue {
56 param_id: 0x0F,
57 value: ParamData::Float40(1.0),
58 },
59 ParamValue {
61 param_id: 0x26,
62 value: ParamData::Float40(0.0381),
63 },
64 ],
65 radar_cal: Some(RadarCal {
66 range_mm: 2743, height_mm: 25, }),
69 }
70}
71
72fn default_cam_config() -> CamConfig {
73 CamConfig {
74 dynamic_config: true,
75 resolution_width: 1024,
76 resolution_height: 768,
77 rotation: 0,
78 ev: 0,
79 quality: 80,
80 framerate: 20,
81 streaming_framerate: 1,
82 ringbuffer_pretime_ms: 1000,
83 ringbuffer_posttime_ms: 4000,
84 raw_camera_mode: 0,
85 fusion_camera_mode: false,
86 raw_shutter_speed_max: 0.0,
87 raw_ev_roi_x: -1,
88 raw_ev_roi_y: -1,
89 raw_ev_roi_width: -1,
90 raw_ev_roi_height: -1,
91 raw_x_offset: -1,
92 raw_bin44: false,
93 raw_live_preview_write_interval_ms: -1,
94 raw_y_offset: -1,
95 buffer_sub_sampling_pre_trigger_div: -1,
96 buffer_sub_sampling_post_trigger_div: -1,
97 buffer_sub_sampling_switch_time_offset: -1.0,
98 buffer_sub_sampling_total_buffer_size: -1,
99 buffer_sub_sampling_pre_trigger_buffer_size: -1,
100 }
101}
102
103fn print_handshake(dsp: &DspSync, avr: &AvrSync, pi: &PiSync) {
108 println!("--- Handshake complete ---");
109 println!(
110 " DSP: type=0x{:02X} pcb={}",
111 dsp.hw_info.dsp_type, dsp.hw_info.pcb
112 );
113 println!(" DSP info: {}", dsp.dev_info.text);
114 println!(" AVR info: {}", avr.dev_info.text);
115 println!(
116 " AVR tilt={:.1}° roll={:.1}°",
117 avr.status.tilt, -avr.status.roll
118 );
119 println!(" PI info: {}", pi.dev_info.text);
120 println!(" SSID: {} password: {}", pi.ssid, pi.password);
121 println!(
122 " Battery: {}% {}",
123 dsp.status.battery_percent(),
124 if dsp.status.external_power() {
125 "(plugged in)"
126 } else {
127 ""
128 },
129 );
130}
131
132fn main() {
137 if let Err(e) = run() {
138 eprintln!("error: {e}");
139 process::exit(1);
140 }
141}
142
143fn send_action(
144 conn: &mut BinaryConnection<std::net::TcpStream>,
145 action: Action,
146) -> Result<(), ConnError> {
147 seq::send_action(conn, action)
148}
149
150fn run() -> Result<(), ConnError> {
151 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 conn.set_on_send(|cmd, dest| println!("\n>> {:?} [{}]", cmd, cmd.debug_hex(dest)));
159 conn.set_on_recv(|env| println!("\n<< {env:?}"));
160
161 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 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 seq::arm(&mut conn)?;
185 println!("=== ARMED — waiting for shots ===");
186
187 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 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 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 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 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 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 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 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 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 Message::PrcData(r) => {
326 println!(" PRC: seq={} points={}", r.sequence, r.points.len());
327 }
328
329 Message::ClubPrc(r) => {
331 println!(" Club PRC: points={}", r.points.len());
332 }
333
334 Message::CamImageAvail(r) => {
336 println!(
337 " Camera: streaming={} fusion={} video={}",
338 r.streaming_available, r.fusion_available, r.video_available,
339 );
340 }
341
342 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 Message::ConfigAck(_) | Message::PiStatus(_) => {}
361
362 _ => {}
364 }
365 }
366
367 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}
376
377fn print_shot(data: &seq::ShotData) {
378 println!("\n === Shot complete ===");
379 if let Some(ref f) = data.flight {
380 println!(
381 " Ball: {:.1} mph Carry: {:.0} yd",
382 ms_to_mph(f.launch_speed),
383 m_to_yd(f.carry_distance),
384 );
385 }
386 if let Some(ref c) = data.club {
387 println!(
388 " Club: {:.1} mph Smash: {:.2}",
389 ms_to_mph(c.pre_club_speed),
390 c.smash_factor,
391 );
392 }
393 println!(" === RE-ARMED ===\n");
394}