1#![allow(non_snake_case)]
2
3use anyhow::anyhow;
4use bincode::deserialize;
5use tokio::{net::UdpSocket, time::timeout};
6use tracing::{debug, error, info};
7use tokio::sync::mpsc;
8
9pub mod checksum;
10pub mod constants;
11pub mod control;
12
13#[derive(Debug)]
14pub struct A8Mini {
16 pub command_socket: UdpSocket,
17 pub http_socket: UdpSocket,
18}
19
20impl A8Mini {
21 pub async fn connect() -> anyhow::Result<Self> {
24 Ok(Self::connect_to(
25 constants::CAMERA_IP,
26 constants::CAMERA_COMMAND_PORT,
27 constants::CAMERA_HTTP_PORT,
28 "8080",
29 "8088",
30 )
31 .await?)
32 }
33
34 pub async fn connect_yapping(
36 max_iter: i32,
37 ) -> anyhow::Result<Self> {
38 for _ in 1..max_iter {
39 let connect_attempt = Self::connect().await;
40 if connect_attempt.is_ok() {
41 return Ok(connect_attempt.unwrap());
42 }
43 }
44
45 Err(anyhow!("max_iter reached".to_string()))
46 }
47
48 pub async fn connect_to(
50 camera_ip: &str,
51 camera_command_port: &str,
52 camera_http_port: &str,
53 local_command_port: &str,
54 local_http_port: &str,
55 ) -> anyhow::Result<Self> {
56 debug!(
57 "Binding command_socket to {} and http_socket to {}.",
58 format!("0.0.0.0:{}", local_command_port),
59 format!("0.0.0.0:{}", local_http_port)
60 );
61
62 let camera: A8Mini = A8Mini {
63 command_socket: UdpSocket::bind(format!("0.0.0.0:{}", local_command_port)).await?,
64 http_socket: UdpSocket::bind(format!("0.0.0.0:{}", local_http_port)).await?,
65 };
66
67 camera
68 .command_socket
69 .connect(format!("{}:{}", camera_ip, camera_command_port))
70 .await?;
71 info!("Connected a8mini command_socket.");
72
73 camera
74 .http_socket
75 .connect(format!("{}:{}", camera_ip, camera_http_port))
76 .await?;
77 info!("Connected a8mini http_socket.");
78
79 Ok(camera)
80 }
81
82 pub async fn send_command_blind<T: control::Command>(
84 &self,
85 command: T,
86 ) -> anyhow::Result<()> {
87
88
89 let send_len = self.command_socket.send(command.to_bytes().as_slice()).await?;
90
91 if send_len == 0 {
92 error!("No command bytes sent.");
93 return Err(anyhow!("No command bytes sent.".to_string()));
94 }
95
96
97
98 Ok(())
99 }
100
101 pub async fn send_command<T: control::Command>(
103 &self,
104 command: T,
105 ) -> anyhow::Result<[u8; constants::RECV_BUFF_SIZE]> {
106 self.send_command_blind(command).await?;
107 let mut recv_buffer = [0; constants::RECV_BUFF_SIZE];
108
109 debug!("Waiting for command response.");
110
111 let recv_len = timeout(
112 constants::RECV_TIMEOUT,
113 self.command_socket.recv(&mut recv_buffer),
114 )
115 .await??;
116 if recv_len == 0 {
117 error!("No command bytes received.");
118 return Err(anyhow!("No bytes received.".to_string()));
119 }
120
121 debug!(
122 "Command response of size {} received successfully: {:?}",
123 recv_len,
124 recv_buffer
125 );
126
127 Ok(recv_buffer)
128 }
129
130 pub async fn get_attitude_information(
132 &self,
133 ) -> anyhow::Result<control::A8MiniAttitude> {
134 let response = self
135 .send_command(control::A8MiniSimpleCommand::AttitudeInformation)
136 .await?;
137
138
139 if response.len() < 20 {
140 return Err(anyhow::anyhow!("Response too short to contain attitude data"));
141 }
142
143 let data_slice = &response[8..20];
144 let attitude_info: control::A8MiniAttitude = deserialize(data_slice)?;
145
146 Ok(attitude_info)
147 }
148
149 pub fn stream_attitude_data(self, target_hz: u64) -> mpsc::Receiver<control::A8MiniAttitude> {
150 let (tx, rx) = mpsc::channel(100);
152
153 let interval_ms = 1000 / target_hz;
155
156 tokio::spawn(async move {
157 let mut buffer = [0u8; 128];
158
159 loop {
160 if let Err(_) = self.send_command_blind(control::A8MiniSimpleCommand::AttitudeInformation).await {
161 }
162
163 let recv_future = self.command_socket.recv_from(&mut buffer);
164 match timeout(std::time::Duration::from_millis(50), recv_future).await {
165 Ok(Ok((len, _))) => {
166 if len >= 20 && buffer[7] == 0x0D {
168 let data_slice = &buffer[8..20];
169 if let Ok(att) = deserialize::<control::A8MiniAttitude>(data_slice) {
170
171 if tx.send(att).await.is_err() {
172 break;
173 }
174 }
175 }
176 },
177 _ => {}
178 }
179
180 tokio::time::sleep(std::time::Duration::from_millis(interval_ms)).await;
182 }
183 });
184
185 rx
186 }
187
188 pub async fn get_firmware_version(&self) -> anyhow::Result<control::A8MiniFirmwareVersion> {
189 let response = self
190 .send_command(control::A8MiniSimpleCommand::FirmwareVersionInformation)
191 .await?;
192
193 if response.len() < 16 {
195 return Err(anyhow::anyhow!("Response too short for firmware version"));
196 }
197
198 let data_slice = &response[8..16];
200 let version_info: control::A8MiniFirmwareVersion = deserialize(data_slice)?;
201
202 Ok(version_info)
203 }
204
205 pub async fn send_http_query<T: control::HTTPQuery>(
207 &self,
208 query: T,
209 ) -> anyhow::Result<control::HTTPResponse> {
210 let response = reqwest::get(query.to_string()).await?;
211 debug!("Waiting for HTTP response.");
212
213 let json = response.json::<control::HTTPResponse>().await?;
214 debug!("Received HTTP response.");
215 Ok(json)
216 }
217
218
219
220 pub async fn send_http_media_query<T: control::HTTPQuery>(
222 &self,
223 query: T,
224 ) -> anyhow::Result<Vec<u8>> {
225 let response = reqwest::get(query.to_string()).await?;
226 info!("Waiting for HTTP response.");
227
228 let image_bytes = response.bytes().await?;
229 info!("Received HTTP response.");
230 Ok(image_bytes.to_vec())
231 }
232
233
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 use std::thread::sleep;
241 use std::time::Duration;
242 use tokio::fs::File;
243 use tokio::io::AsyncWriteExt;
244
245
246 #[ignore]
247 #[tokio::test]
248 async fn test_take_and_download_photo() -> anyhow::Result<()> {
249 let cam: A8Mini = A8Mini::connect().await?;
250
251 cam.send_command(control::A8MiniSimpleCommand::TakePicture)
252 .await?;
253 sleep(Duration::from_millis(500));
254 let num_pictures = cam
255 .send_http_query(control::A8MiniSimpleHTTPQuery::GetMediaCountPhotos)
256 .await?
257 .data
258 .count
259 .unwrap();
260 let picture_bytes = cam
261 .send_http_media_query(control::A8MiniComplexHTTPQuery::GetPhoto(num_pictures as u32))
262 .await?;
263 File::create("tmp.jpeg")
264 .await?
265 .write_all(&picture_bytes)
266 .await?;
267
268 Ok(())
269 }
270
271 #[ignore]
272 #[tokio::test]
273 async fn test_send_simple_commands_blind() -> anyhow::Result<()> {
274 let cam: A8Mini = A8Mini::connect().await?;
275
276 cam.send_command_blind(control::A8MiniSimpleCommand::RotateLeft).await?;
277 sleep(Duration::from_millis(500));
278
279 cam.send_command_blind(control::A8MiniSimpleCommand::RotateRight).await?;
280 sleep(Duration::from_millis(1000));
281
282 cam.send_command_blind(control::A8MiniSimpleCommand::RotateLeft).await?;
283 sleep(Duration::from_millis(500));
284
285 cam.send_command_blind(control::A8MiniSimpleCommand::StopRotation).await?;
286
287 cam.send_command_blind(control::A8MiniSimpleCommand::RotateUp).await?;
288 sleep(Duration::from_millis(500));
289
290 cam.send_command_blind(control::A8MiniSimpleCommand::RotateDown).await?;
291 sleep(Duration::from_millis(500));
292
293 cam.send_command_blind(control::A8MiniSimpleCommand::StopRotation).await?;
294 sleep(Duration::from_millis(1000));
295
296 cam.send_command_blind(control::A8MiniSimpleCommand::AutoCenter).await?;
297 Ok(())
298 }
299
300 #[ignore]
301 #[tokio::test]
302 async fn test_send_complex_commands_blind() -> anyhow::Result<()> {
303 let cam: A8Mini = A8Mini::connect().await?;
304
305 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchSpeed(50, 50)).await?;
306 sleep(Duration::from_millis(1000));
307
308 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchSpeed(50, 10)).await?;
309 sleep(Duration::from_millis(1000));
310
311 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchSpeed(-25, -15)).await?;
312 sleep(Duration::from_millis(6000));
313
314 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchSpeed(0, 0)).await?;
315 sleep(Duration::from_millis(1000));
316
317 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchAngle(90, 0)).await?;
318 sleep(Duration::from_millis(1000));
319
320 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchAngle(90, -90)).await?;
321 sleep(Duration::from_millis(1000));
322
323 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchAngle(-90, -90)).await?;
324 sleep(Duration::from_millis(1000));
325
326 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchAngle(-90, 0)).await?;
327 sleep(Duration::from_millis(1000));
328
329 cam.send_command_blind(control::A8MiniComplexCommand::SetYawPitchAngle(0, 0)).await?;
330 sleep(Duration::from_millis(1000));
331
332 cam.send_command_blind(control::A8MiniSimpleCommand::AutoCenter).await?;
333 Ok(())
334 }
335
336 #[ignore]
337 #[tokio::test]
338 async fn test_send_command_with_ack() -> anyhow::Result<()> {
339 let cam: A8Mini = A8Mini::connect().await?;
340 println!("{:?}", cam.get_attitude_information().await?);
341 Ok(())
342 }
343}