1use crate::{checksum, constants};
2use serde::{Deserialize, Serialize};
3use std::fmt;
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 GimbalStatus = 31,
50}
51
52impl Command for A8MiniSimpleCommand {
53 fn to_bytes(&self) -> Vec<u8> {
54 constants::HARDCODED_COMMANDS[*self as usize].to_vec()
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum A8MiniComplexCommand {
61 SetYawPitchSpeed(i8, i8),
62 SetYawPitchAngle(i16, i16),
63 SetTimeUTC(u64),
64 GetCodecSpecs(u8), SetCodecSpecs(u8, u8, u16, u16, u16, u8), RequestGimbalDataStream(u8, u8), }
68
69impl Command for A8MiniComplexCommand {
70 fn to_bytes(&self) -> Vec<u8> {
71 match *self {
72 A8MiniComplexCommand::SetYawPitchSpeed(v_yaw, v_pitch) => {
73 let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x07];
74
75 byte_arr.push(v_yaw.clamp(-100, 100) as u8);
76 byte_arr.push(v_pitch.clamp(-100, 100) as u8);
77
78 byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
79
80 byte_arr
81 }
82 A8MiniComplexCommand::SetYawPitchAngle(theta_yaw, theta_pitch) => {
83 let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0e];
84
85 byte_arr.extend_from_slice(&theta_yaw.clamp(-1350, 1350).to_le_bytes());
86 byte_arr.extend_from_slice(&theta_pitch.clamp(-900, 250).to_le_bytes());
87
88 byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
89
90 byte_arr
91 }
92 A8MiniComplexCommand::SetTimeUTC(timestamp) => {
93 let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x30];
94
95 byte_arr.extend_from_slice(×tamp.to_le_bytes());
96
97 byte_arr
98 }
99 A8MiniComplexCommand::GetCodecSpecs(stream_type) => {
100 let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x20];
101
102 byte_arr.extend_from_slice(&stream_type.clamp(0, 2).to_le_bytes());
103
104 byte_arr
105 }
106 A8MiniComplexCommand::SetCodecSpecs(
107 stream_type,
108 video_enc_type,
109 resolution_l,
110 resolution_h,
111 video_bitrate,
112 _,
113 ) => {
114 let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x21];
115
116 byte_arr.extend_from_slice(&stream_type.clamp(0, 2).to_le_bytes());
117 byte_arr.extend_from_slice(&video_enc_type.clamp(1, 2).to_le_bytes());
118
119 byte_arr.extend_from_slice(&resolution_l.to_le_bytes());
121 byte_arr.extend_from_slice(&resolution_h.to_le_bytes());
122
123 byte_arr.extend_from_slice(&video_bitrate.to_le_bytes());
125
126 byte_arr
127 }
128 A8MiniComplexCommand::RequestGimbalDataStream(data_type, data_freq) => {
130 let mut byte_arr: Vec<u8> = vec![0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x25];
132
133 byte_arr.push(data_type);
134 byte_arr.push(data_freq);
135
136 byte_arr.extend_from_slice(&checksum::crc16_calc(&byte_arr, 0));
137 byte_arr
138 }
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub enum A8MiniSimpleHTTPQuery {
146 GetDirectoriesPhotos,
147 GetDirectoriesVideos,
148 GetMediaCountPhotos,
149 GetMediaCountVideos,
150}
151
152impl HTTPQuery for A8MiniSimpleHTTPQuery {
153 fn to_string(&self) -> String {
154 match *self {
155 A8MiniSimpleHTTPQuery::GetDirectoriesPhotos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getdirectories?media_type=0".to_string(),
156 A8MiniSimpleHTTPQuery::GetDirectoriesVideos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getdirectories?media_type=1".to_string(),
157 A8MiniSimpleHTTPQuery::GetMediaCountPhotos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getmediacount?media_type=0&path=101SIYI_IMG".to_string(),
158 A8MiniSimpleHTTPQuery::GetMediaCountVideos => "http://192.168.144.25:82/cgi-bin/media.cgi/api/v1/getmediacount?media_type=1&path=100SIYI_VID".to_string(),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum A8MiniComplexHTTPQuery {
166 GetPhoto(u32),
167 GetVideo(u32),
168}
169
170impl HTTPQuery for A8MiniComplexHTTPQuery {
171 fn to_string(&self) -> String {
172 match *self {
173 A8MiniComplexHTTPQuery::GetPhoto(photo_ind) => format!(
174 "http://192.168.144.25:82/photo/101SIYI_IMG/IMG_{:0>4}.jpg",
175 photo_ind
176 ),
177 A8MiniComplexHTTPQuery::GetVideo(video_ind) => format!(
178 "http://192.168.144.25:82/photo/100SIYI_VID/REC_{:0>4}.mp4",
179 video_ind
180 ),
181 }
182 }
183}
184
185#[derive(Debug, Serialize, Deserialize)]
187pub struct HTTPResponse {
188 pub code: i32,
189 pub data: HTTPResponseData,
190 pub success: bool,
191 pub message: String,
192}
193
194#[derive(Debug, Serialize, Deserialize)]
196pub struct HTTPResponseData {
197 pub media_type: i32,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub directories: Option<String>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub path: Option<String>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub start: Option<i32>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub count: Option<i32>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub list: Option<String>,
208}
209
210#[derive(Debug, PartialEq, Eq, Deserialize)]
211pub struct A8MiniFirmwareVersion {
212 pub code_ver_byte0: u8, pub code_ver_byte1: u8, pub code_ver_byte2: u8, pub code_ver_byte3: u8, pub gimbal_ver_byte0: u8, pub gimbal_ver_byte1: u8, pub gimbal_ver_byte2: u8, pub gimbal_ver_byte3: u8, }
224
225impl fmt::Display for A8MiniFirmwareVersion {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 write!(
229 f,
230 "FIRMWARE VERSION:\n\tCamera: {}.{}.{}\n\tGimbal: {}.{}.{} (Build {})",
231 self.code_ver_byte2, self.code_ver_byte1, self.code_ver_byte0,
233 self.gimbal_ver_byte2, self.gimbal_ver_byte1, self.gimbal_ver_byte0, self.gimbal_ver_byte3
235 )
236 }
237}
238#[derive(Debug, PartialEq, Eq, Deserialize)]
240pub struct A8MiniAttitude {
241 pub theta_yaw: i16,
242 pub theta_pitch: i16,
243 pub theta_roll: i16,
244 pub v_yaw: i16,
245 pub v_pitch: i16,
246 pub v_roll: i16,
247}
248
249impl fmt::Display for A8MiniAttitude {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 let yaw_deg = self.theta_yaw as f32 / 10.0;
252 let pitch_deg = self.theta_pitch as f32 / 10.0;
253 let roll_deg = self.theta_roll as f32 / 10.0;
254
255 write!(
256 f,
257 "GIMBAL ATTITUDE:\n\tYaw: {:.1}°\n\tPitch: {:.1}°\n\tRoll: {:.1}°\n\t(Speeds: Y={}, P={}, R={})",
258 yaw_deg, pitch_deg, roll_deg, self.v_yaw, self.v_pitch, self.v_roll
259 )
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_complex_command_creation_angle() {
269 let computed_command = A8MiniComplexCommand::SetYawPitchAngle(130, -20).to_bytes();
270 let expected_command: [u8; 14] = [
271 0x55, 0x66, 0x01, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x82, 0xff, 0xec, 0x8f, 0xad,
272 ];
273 assert_eq!(computed_command, expected_command);
274 }
275
276 #[test]
277 fn test_complex_command_creation_speed() {
278 let computed_command = A8MiniComplexCommand::SetYawPitchSpeed(104, -20).to_bytes();
279 let expected_command: [u8; 12] = [
280 0x55, 0x66, 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x64, 0xec, 0xbd, 0xdf,
281 ];
282 assert_eq!(computed_command, expected_command);
283 }
284
285 #[test]
286 fn test_byte_deserialization() {
287 let attitude_bytes: &[u8] = &[
288 0x28, 0x00, 0x32, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00,
289 ];
290
291 let computed_attitude_info: A8MiniAttitude = bincode::deserialize(attitude_bytes).unwrap();
293
294 let expected_attitude_info = A8MiniAttitude {
295 theta_yaw: 40,
296 theta_pitch: 50,
297 theta_roll: 60,
298 v_yaw: 4,
299 v_pitch: 5,
300 v_roll: 6,
301 };
302
303 assert_eq!(computed_attitude_info, expected_attitude_info);
304 }
305}