1use std::net::SocketAddr;
8use std::process;
9use std::time::{Duration, Instant};
10
11use ironsight::client::{BinaryClient, BinaryEvent};
12use ironsight::conn::DEFAULT_ADDR;
13use ironsight::protocol::camera::CamConfig;
14use ironsight::protocol::config::{
15 MODE_CHIPPING, MODE_OUTDOOR, MODE_PUTTING, ParamData, ParamValue, RadarCal,
16};
17use ironsight::seq::AvrSettings;
18use ironsight::BinaryConnection;
19
20#[derive(Debug, Clone, Copy, PartialEq)]
23enum Step {
24 InitialArm,
26 WaitKeepalive,
28 ModeChange1,
30 WaitKeepalive2,
32 ModeChange2,
34 WaitKeepalive3,
36 Done,
38}
39
40fn main() {
41 if let Err(e) = run() {
42 eprintln!("\n✗ FAILED: {e}");
43 process::exit(1);
44 }
45}
46
47fn run() -> Result<(), ironsight::ConnError> {
48 let addr: SocketAddr = DEFAULT_ADDR.parse().unwrap();
49 println!("=== Mode Change Integration Test ===\n");
50
51 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 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 let mut handshake_done = false;
79 let mut configure_count = 0u8; 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 (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 step = Step::WaitKeepalive;
117 phase_start = Instant::now();
118 println!("\n[step] WaitKeepalive: waiting for keepalive...");
119 }
120
121 (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 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 (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 (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 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 (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 (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 (Step::Done, _) => unreachable!(),
197
198 (_, BinaryEvent::Message(_)) => {}
200 (_, BinaryEvent::Keepalive(_)) => {} (_, 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}
221
222fn avr_settings_full(mode: u8) -> AvrSettings {
225 AvrSettings {
226 mode,
227 params: vec![
228 ParamValue {
229 param_id: 0x06,
230 value: ParamData::Int24(0), },
232 ParamValue {
233 param_id: 0x0F,
234 value: ParamData::Float40(0.8), },
236 ParamValue {
237 param_id: 0x26,
238 value: ParamData::Float40(0.0381), },
240 ],
241 radar_cal: Some(RadarCal {
242 range_mm: 2133, height_mm: 0,
244 }),
245 }
246}
247
248fn avr_settings_mode_only(mode: u8) -> AvrSettings {
251 AvrSettings {
252 mode,
253 params: vec![],
254 radar_cal: None,
255 }
256}
257
258fn cam_config() -> CamConfig {
259 CamConfig {
260 dynamic_config: true,
261 resolution_width: 1024,
262 resolution_height: 768,
263 rotation: 0,
264 ev: 0,
265 quality: 80,
266 framerate: 20,
267 streaming_framerate: 1,
268 ringbuffer_pretime_ms: 1000,
269 ringbuffer_posttime_ms: 4000,
270 raw_camera_mode: 0,
271 fusion_camera_mode: false,
272 raw_shutter_speed_max: 0.0,
273 raw_ev_roi_x: 0,
274 raw_ev_roi_y: 0,
275 raw_ev_roi_width: 0,
276 raw_ev_roi_height: 0,
277 raw_x_offset: 0,
278 raw_bin44: false,
279 raw_live_preview_write_interval_ms: 0,
280 raw_y_offset: 0,
281 buffer_sub_sampling_pre_trigger_div: 1,
282 buffer_sub_sampling_post_trigger_div: 1,
283 buffer_sub_sampling_switch_time_offset: 0.0,
284 buffer_sub_sampling_total_buffer_size: 0,
285 buffer_sub_sampling_pre_trigger_buffer_size: 0,
286 }
287}