a8mini_camera_rs/
control.rs

1use crate::{checksum, constants};
2use serde::{Deserialize, Serialize};
3
4
5/// Trait for camera commands
6pub trait Command {
7    fn to_bytes(&self) -> Vec<u8>;
8}
9
10/// Trait for HTTP API queries
11pub trait HTTPQuery {
12    fn to_string(&self) -> String;
13}
14
15/// Enums for hardcoded simple commands.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum A8MiniSimpleCommand {
18    AutoCenter = 0,   // handled ACK (sta)
19    RotateUp = 1,     // handled ACK (sta)
20    RotateDown = 2,   // handled ACK (sta)
21    RotateRight = 3,  // handled ACK (sta)
22    RotateLeft = 4,   // handled ACK (sta)
23    StopRotation = 5, // handled ACK (sta)
24    ZoomIn = 6,       // handled ACK (sta)
25    ZoomOut = 7,      // handled ACK (sta)
26    ZoomMax = 8,
27    MaxZoomInformation = 9,
28    FocusIn = 10,
29    FocusOut = 11,
30    TakePicture = 12, // no ACK
31    RecordVideo = 13, // no ACK
32    Rotate100100 = 14,
33    CameraInformation = 15,
34    AutoFocus = 16,   // handled ACK (sta)
35    HardwareIDInformation = 17,
36    FirmwareVersionInformation = 18,
37    SetLockMode = 19,
38    SetFollowMode = 20,
39    SetFPVMode = 21,
40    AttitudeInformation = 22,
41    SetVideoOutputHDMI = 23,
42    SetVideoOutputCVBS = 24,
43    SetVideoOutputOff = 25,
44    LaserRangefinderInformation = 26,
45    RebootCamera = 27,
46    RebootGimbal = 28,
47}
48
49impl Command for A8MiniSimpleCommand {
50    fn to_bytes(&self) -> Vec<u8> {
51        constants::HARDCODED_COMMANDS[*self as usize].to_vec()
52    }
53}
54
55/// Enums for commands that require continuous values for data field.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum A8MiniComplexCommand {
58    SetYawPitchSpeed(i8, i8),
59    SetYawPitchAngle(i16, i16),
60}
61
62impl Command for A8MiniComplexCommand {
63    fn to_bytes(&self) -> Vec<u8> {
64        match *self {
65            A8MiniComplexCommand::SetYawPitchSpeed(v_yaw, v_pitch) => {
66                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x07];
67
68                byte_arr.push(v_yaw.clamp(-100, 100) as u8);
69                byte_arr.push(v_pitch.clamp(-100, 100) as u8);
70
71                byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
72
73                byte_arr
74            }
75            A8MiniComplexCommand::SetYawPitchAngle(theta_yaw, theta_pitch) => {
76                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0e];
77
78                byte_arr.extend_from_slice(&theta_yaw.clamp(-1350, 1350).to_be_bytes());
79                byte_arr.extend_from_slice(&theta_pitch.clamp(-900, 250).to_be_bytes());
80
81                byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
82
83                byte_arr
84            }
85        }
86    }
87}
88
89/// Enums for simple HTTP queries
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum A8MiniSimpleHTTPQuery {
92    GetDirectoriesPhotos,
93    GetDirectoriesVideos,
94    GetMediaCountPhotos,
95    GetMediaCountVideos,
96}
97
98impl HTTPQuery for A8MiniSimpleHTTPQuery {
99    fn to_string(&self) -> String {
100        match *self {
101            A8MiniSimpleHTTPQuery::GetDirectoriesPhotos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getdirectories?media_type=0".to_string(),
102            A8MiniSimpleHTTPQuery::GetDirectoriesVideos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getdirectories?media_type=1".to_string(),
103            A8MiniSimpleHTTPQuery::GetMediaCountPhotos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getmediacount?media_type=0&path=101SIYI_IMG".to_string(),
104            A8MiniSimpleHTTPQuery::GetMediaCountVideos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getmediacount?media_type=1&path=100SIYI_VID".to_string(),
105        }
106    }
107}
108
109/// Enums for complex HTTP queries
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum A8MiniComplexHTTPQuery {
112    GetPhoto(u8),
113    GetVideo(u8),
114}
115
116impl HTTPQuery for A8MiniComplexHTTPQuery {
117    fn to_string(&self) -> String {
118        match *self {
119            A8MiniComplexHTTPQuery::GetPhoto(photo_ind) => format!(
120                "http://192.168.144.25:82/photo/101SIYI_IMG/IMG_{:0>4}.jpg",
121                photo_ind
122            ),
123            A8MiniComplexHTTPQuery::GetVideo(video_ind) => format!(
124                "http://192.168.144.25:82/video/100SIYI_VID/REC_{:0>4}.mp4",
125                video_ind
126            ),
127        }
128    }
129}
130
131/// Response json format
132#[derive(Debug, Serialize, Deserialize)]
133pub struct HTTPResponse {
134    pub code: i32,
135    pub data: HTTPResponseData,
136    pub success: bool,
137    pub message: String,
138}
139
140/// Response json data format
141#[derive(Debug, Serialize, Deserialize)]
142pub struct HTTPResponseData {
143    pub media_type: i32,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub directories: Option<String>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub path: Option<String>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub start: Option<i32>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub count: Option<i32>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub list: Option<String>,
154}
155
156/// Camera attitude information
157#[derive(Debug, PartialEq, Eq, Deserialize)]
158pub struct A8MiniAtittude {
159    pub theta_yaw: i16,
160    pub theta_pitch: i16,
161    pub theta_roll: i16,
162    pub v_yaw: i16,
163    pub v_pitch: i16,
164    pub v_roll: i16,
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_complex_command_creation_angle() {
173        let computed_command = A8MiniComplexCommand::SetYawPitchAngle(130, -20).to_bytes();
174        let expected_command: [u8; 14] = [
175            0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x82, 0xff, 0xec, 0x8f, 0xad,
176        ];
177        assert_eq!(computed_command, expected_command);
178    }
179
180    #[test]
181    fn test_complex_command_creation_speed() {
182        let computed_command = A8MiniComplexCommand::SetYawPitchSpeed(104, -20).to_bytes();
183        let expected_command: [u8; 12] = [
184            0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x64, 0xec, 0xbd, 0xdf,
185        ];
186        assert_eq!(computed_command, expected_command);
187    }
188
189    #[test]
190    fn test_byte_deserialization() {
191        let attitude_bytes: &[u8] = &[
192            0x28, 0x00, 0x32, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00,
193        ];
194
195        // Note: little endian deserialize
196        let computed_attitude_info: A8MiniAtittude = bincode::deserialize(attitude_bytes).unwrap();
197
198        let expected_attitude_info = A8MiniAtittude {
199            theta_yaw: 40,
200            theta_pitch: 50,
201            theta_roll: 60,
202            v_yaw: 4,
203            v_pitch: 5,
204            v_roll: 6,
205        };
206
207        assert_eq!(computed_attitude_info, expected_attitude_info);
208    }
209}