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    Resolution4k = 29,
48    Heartbeat = 30,
49}
50
51impl Command for A8MiniSimpleCommand {
52    fn to_bytes(&self) -> Vec<u8> {
53        constants::HARDCODED_COMMANDS[*self as usize].to_vec()
54    }
55}
56
57/// Enums for commands that require continuous values for data field.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum A8MiniComplexCommand {
60    SetYawPitchSpeed(i8, i8),
61    SetYawPitchAngle(i16, i16),
62    SetTimeUTC(u64),
63    GetCodecSpecs(u8), // TODO: WIP
64    SetCodecSpecs(u8, u8, u16, u16, u16, u8), // TODO: WIP
65}
66
67impl Command for A8MiniComplexCommand {
68    fn to_bytes(&self) -> Vec<u8> {
69        match *self {
70            A8MiniComplexCommand::SetYawPitchSpeed(v_yaw, v_pitch) => {
71                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x07];
72
73                byte_arr.push(v_yaw.clamp(-100, 100) as u8);
74                byte_arr.push(v_pitch.clamp(-100, 100) as u8);
75
76                byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
77
78                byte_arr
79            },
80            A8MiniComplexCommand::SetYawPitchAngle(theta_yaw, theta_pitch) => {
81                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0e];
82
83                byte_arr.extend_from_slice(&theta_yaw.clamp(-1350, 1350).to_be_bytes());
84                byte_arr.extend_from_slice(&theta_pitch.clamp(-900, 250).to_be_bytes());
85
86                byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
87
88                byte_arr
89            },
90            A8MiniComplexCommand::SetTimeUTC(timestamp) => {
91                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x30];
92
93                byte_arr.extend_from_slice(&timestamp.to_be_bytes());
94
95                byte_arr
96            },
97            A8MiniComplexCommand::GetCodecSpecs(stream_type) => {
98                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x20];
99
100                byte_arr.extend_from_slice(&stream_type.clamp(0, 2).to_be_bytes());
101
102                byte_arr
103            },
104            A8MiniComplexCommand::SetCodecSpecs(stream_type, video_enc_type, resolution_l, resolution_h, video_bitrate, _) => {
105                let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x21];
106
107                byte_arr.extend_from_slice(&stream_type.clamp(0, 2).to_be_bytes());
108                byte_arr.extend_from_slice(&video_enc_type.clamp(1, 2).to_be_bytes());
109
110                // TODO: make sure resolution_l and resolution_h are clamped to only 1920/1280 and 1080/720 respectively
111                byte_arr.extend_from_slice(&resolution_l.to_be_bytes());
112                byte_arr.extend_from_slice(&resolution_h.to_be_bytes());
113                
114                // TODO: make sure video bitrate is reasonable
115                byte_arr.extend_from_slice(&video_bitrate.to_be_bytes());
116
117                byte_arr
118            },
119        }
120    }
121}
122
123/// Enums for simple HTTP queries
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum A8MiniSimpleHTTPQuery {
126    GetDirectoriesPhotos,
127    GetDirectoriesVideos,
128    GetMediaCountPhotos,
129    GetMediaCountVideos,
130}
131
132impl HTTPQuery for A8MiniSimpleHTTPQuery {
133    fn to_string(&self) -> String {
134        match *self {
135            A8MiniSimpleHTTPQuery::GetDirectoriesPhotos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getdirectories?media_type=0".to_string(),
136            A8MiniSimpleHTTPQuery::GetDirectoriesVideos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getdirectories?media_type=1".to_string(),
137            A8MiniSimpleHTTPQuery::GetMediaCountPhotos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getmediacount?media_type=0&path=101SIYI_IMG".to_string(),
138            A8MiniSimpleHTTPQuery::GetMediaCountVideos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getmediacount?media_type=1&path=100SIYI_VID".to_string(),
139        }
140    }
141}
142
143/// Enums for complex HTTP queries
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub enum A8MiniComplexHTTPQuery {
146    GetPhoto(u8),
147    GetVideo(u8),
148}
149
150impl HTTPQuery for A8MiniComplexHTTPQuery {
151    fn to_string(&self) -> String {
152        match *self {
153            A8MiniComplexHTTPQuery::GetPhoto(photo_ind) => format!(
154                "http://192.168.144.25:82/photo/101SIYI_IMG/IMG_{:0>4}.jpg",
155                photo_ind
156            ),
157            A8MiniComplexHTTPQuery::GetVideo(video_ind) => format!(
158                "http://192.168.144.25:82/photo/100SIYI_VID/REC_{:0>4}.mp4",
159                video_ind
160            ),
161        }
162    }
163}
164
165/// Response json format
166#[derive(Debug, Serialize, Deserialize)]
167pub struct HTTPResponse {
168    pub code: i32,
169    pub data: HTTPResponseData,
170    pub success: bool,
171    pub message: String,
172}
173
174/// Response json data format
175#[derive(Debug, Serialize, Deserialize)]
176pub struct HTTPResponseData {
177    pub media_type: i32,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub directories: Option<String>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub path: Option<String>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub start: Option<i32>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub count: Option<i32>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub list: Option<String>,
188}
189
190/// Camera attitude information
191#[derive(Debug, PartialEq, Eq, Deserialize)]
192pub struct A8MiniAtittude {
193    pub theta_yaw: i16,
194    pub theta_pitch: i16,
195    pub theta_roll: i16,
196    pub v_yaw: i16,
197    pub v_pitch: i16,
198    pub v_roll: i16,
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_complex_command_creation_angle() {
207        let computed_command = A8MiniComplexCommand::SetYawPitchAngle(130, -20).to_bytes();
208        let expected_command: [u8; 14] = [
209            0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x82, 0xff, 0xec, 0x8f, 0xad,
210        ];
211        assert_eq!(computed_command, expected_command);
212    }
213
214    #[test]
215    fn test_complex_command_creation_speed() {
216        let computed_command = A8MiniComplexCommand::SetYawPitchSpeed(104, -20).to_bytes();
217        let expected_command: [u8; 12] = [
218            0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x64, 0xec, 0xbd, 0xdf,
219        ];
220        assert_eq!(computed_command, expected_command);
221    }
222
223    #[test]
224    fn test_byte_deserialization() {
225        let attitude_bytes: &[u8] = &[
226            0x28, 0x00, 0x32, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00,
227        ];
228
229        // Note: little endian deserialize
230        let computed_attitude_info: A8MiniAtittude = bincode::deserialize(attitude_bytes).unwrap();
231
232        let expected_attitude_info = A8MiniAtittude {
233            theta_yaw: 40,
234            theta_pitch: 50,
235            theta_roll: 60,
236            v_yaw: 4,
237            v_pitch: 5,
238            v_roll: 6,
239        };
240
241        assert_eq!(computed_attitude_info, expected_attitude_info);
242    }
243}