Skip to main content

a8mini_camera_rs/
lib.rs

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)]
14/// Represents the A8Mini camera API with a dedicate UDP socket for both `Command`s and `HTTPQuery`s.
15pub struct A8Mini {
16    pub command_socket: UdpSocket,
17    pub http_socket: UdpSocket,
18}
19
20impl A8Mini {
21    /// Connect to and creates a new `A8Mini` using default ip address `192.168.144.25` and default port 37260 and port 82. 
22    /// Remote ports are mapped to port 8080 and port 8088 on local.
23    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    /// Repeatedly tries to reconnect a total of `max_iter`` times
35    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    /// Connects to and creates a new `A8Mini` given network args.
49    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    /// Sends a `control::Command` blind. This should be used for all commands that don't have a ACK.
83    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    /// Sends a `control::Command` expecting an ACK. Returns received ACK response bytes.
102    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    /// Retrieves attitude information from the camera. 
131    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        // Create a channel with a buffer of 100 packets
151        let (tx, rx) = mpsc::channel(100);
152        
153        // Calculate sleep time (e.g., 100Hz = 10ms)
154        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                        // Check for correct Packet ID (0x0D)
167                        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                // maintain Frequency
181                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        // Header is 8 bytes. Payload is 8 bytes. Total packet should be at least 16 bytes.
194        if response.len() < 16 {
195             return Err(anyhow::anyhow!("Response too short for firmware version"));
196        }
197        
198        // Slice the payload (Indices 8 to 16)
199        let data_slice = &response[8..16];
200        let version_info: control::A8MiniFirmwareVersion = deserialize(data_slice)?;
201        
202        Ok(version_info)
203    }
204
205    /// Sends a `control::HTTPQuery` and returns the corresponding received `control::HTTPResponse`.
206    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    /// Retrieves an image or video (WIP) from the camera.
221    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}