1use crate::{checksum, constants};
2use serde::{Deserialize, Serialize};
3
4
5pub trait Command {
7 fn to_bytes(&self) -> Vec<u8>;
8}
9
10pub trait HTTPQuery {
12 fn to_string(&self) -> String;
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum A8MiniSimpleCommand {
18 AutoCenter = 0, RotateUp = 1, RotateDown = 2, RotateRight = 3, RotateLeft = 4, StopRotation = 5, ZoomIn = 6, ZoomOut = 7, ZoomMax = 8,
27 MaxZoomInformation = 9,
28 FocusIn = 10,
29 FocusOut = 11,
30 TakePicture = 12, RecordVideo = 13, Rotate100100 = 14,
33 CameraInformation = 15,
34 AutoFocus = 16, 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum A8MiniComplexCommand {
60 SetYawPitchSpeed(i8, i8),
61 SetYawPitchAngle(i16, i16),
62 SetTimeUTC(u64),
63 GetCodecSpecs(u8), SetCodecSpecs(u8, u8, u16, u16, u16, u8), }
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(×tamp.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 byte_arr.extend_from_slice(&resolution_l.to_be_bytes());
112 byte_arr.extend_from_slice(&resolution_h.to_be_bytes());
113
114 byte_arr.extend_from_slice(&video_bitrate.to_be_bytes());
116
117 byte_arr
118 },
119 }
120 }
121}
122
123#[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#[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#[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#[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#[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 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}