dji_log_parser/frame/
mod.rs

1use serde::Serialize;
2#[cfg(target_arch = "wasm32")]
3use tsify_next::Tsify;
4
5use crate::layout::details::Details;
6use crate::record::osd::{AppCommand, GroundOrSky};
7use crate::record::smart_battery_group::SmartBatteryGroup;
8use crate::record::Record;
9use crate::utils::append_message;
10
11mod app;
12mod battery;
13mod camera;
14mod custom;
15mod details;
16mod gimbal;
17mod home;
18mod osd;
19mod rc;
20mod recover;
21
22pub use app::FrameApp;
23pub use battery::FrameBattery;
24pub use camera::FrameCamera;
25pub use custom::FrameCustom;
26pub use details::FrameDetails;
27pub use gimbal::FrameGimbal;
28pub use home::FrameHome;
29pub use osd::FrameOSD;
30pub use rc::FrameRC;
31pub use recover::FrameRecover;
32
33/// Represents a normalized frame of data from a DJI log.
34///
35/// A `Frame` is a standardized representation of log data, normalized across
36/// different log versions. It provides a consistent and easy-to-use format
37/// for analyzing and processing DJI log information.
38///
39#[derive(Serialize, Debug, Default, Clone)]
40#[serde(rename_all = "camelCase")]
41#[cfg_attr(target_arch = "wasm32", derive(Tsify))]
42pub struct Frame {
43    pub custom: FrameCustom,
44    pub osd: FrameOSD,
45    pub gimbal: FrameGimbal,
46    pub camera: FrameCamera,
47    pub rc: FrameRC,
48    pub battery: FrameBattery,
49    pub home: FrameHome,
50    pub recover: FrameRecover,
51    pub app: FrameApp,
52}
53
54impl Frame {
55    /// Resets event-related values of the `Frame` instance.
56    ///
57    /// This method resets the state of the camera, application tips, and warnings.
58    /// Additionally, if the battery cell voltage is estimated, it resets all cell voltages to zero.
59    ///
60    fn reset(&mut self) {
61        self.camera.is_photo = bool::default();
62        self.app.tip = String::default();
63        self.app.warn = String::default();
64
65        if self.battery.is_cell_voltage_estimated {
66            self.battery.cell_voltages.fill(0.0);
67        }
68    }
69
70    /// Computes derived values based on the current state of the `Frame` instance.
71    ///
72    /// This method finalizes the state of the `Frame` by computing any values that are
73    /// derived from the current attributes. This is typically called after all primary
74    /// attributes have been set or modified.
75    ///
76    fn finalize(&mut self) {
77        if self.osd.height_max < self.osd.height {
78            self.osd.height_max = self.osd.height;
79        }
80        if self.osd.x_speed_max < self.osd.x_speed {
81            self.osd.x_speed_max = self.osd.x_speed;
82        }
83        if self.osd.y_speed_max < self.osd.y_speed {
84            self.osd.y_speed_max = self.osd.y_speed;
85        }
86        if self.osd.z_speed_max < self.osd.z_speed {
87            self.osd.z_speed_max = self.osd.z_speed;
88        }
89
90        if let Some(first_cell) = self.battery.cell_voltages.first() {
91            if *first_cell == 0.0 && self.battery.voltage > 0.0 {
92                self.battery.is_cell_voltage_estimated = true;
93                self.battery
94                    .cell_voltages
95                    .fill(self.battery.voltage / self.battery.cell_num as f32)
96            }
97        }
98
99        if self.battery.temperature > self.battery.max_temperature {
100            self.battery.max_temperature = self.battery.temperature
101        }
102
103        if self.battery.temperature < self.battery.min_temperature
104            || self.battery.min_temperature == f32::default()
105        {
106            self.battery.min_temperature = self.battery.temperature
107        }
108
109        let max_voltage = self
110            .battery
111            .cell_voltages
112            .iter()
113            .copied()
114            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
115            .unwrap_or(0.0);
116
117        let min_voltage = self
118            .battery
119            .cell_voltages
120            .iter()
121            .copied()
122            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
123            .unwrap_or(0.0);
124
125        self.battery.cell_voltage_deviation =
126            ((max_voltage - min_voltage) * 1000.0).round() / 1000.0;
127
128        if self.battery.cell_voltage_deviation > self.battery.max_cell_voltage_deviation {
129            self.battery.max_cell_voltage_deviation = self.battery.cell_voltage_deviation;
130        }
131    }
132}
133
134/// Converts a vector of `Record` objects into a vector of `Frame` objects.
135///
136/// This function takes a list of `Record` objects and transforms each one into a
137/// corresponding `Frame` object. The transformation process normalizes the data
138/// across different log versions, creating a standardized format that's easier
139/// to work with.
140///
141/// # Arguments
142/// - `records`: A vector of `Record` objects representing the raw log data.
143///
144/// # Returns
145/// - `Vec<Frame>`: A vector of `Frame` objects representing the normalized log data.
146///   Each `Frame` corresponds to one or more `Record` objects, depending on the
147///   specific normalization logic.
148///
149pub fn records_to_frames(records: Vec<Record>, details: Details) -> Vec<Frame> {
150    let mut frames = Vec::new();
151    let mut frame = Frame {
152        battery: FrameBattery {
153            cell_num: details.product_type.battery_cell_num(),
154            cell_voltages: vec![0.0; details.product_type.battery_cell_num() as usize],
155            is_cell_voltage_estimated: true,
156            ..FrameBattery::default()
157        },
158        ..Frame::default()
159    };
160
161    let mut frame_index = 0;
162
163    for record in records {
164        match record {
165            Record::OSD(osd) => {
166                if frame_index > 0 {
167                    frame.finalize();
168                    frames.push(frame.clone());
169                    frame.reset();
170                }
171
172                // Fill OSD record
173                frame.osd.fly_time = osd.fly_time;
174                frame.osd.latitude = osd.latitude;
175                frame.osd.longitude = osd.longitude;
176                // Fix altitude by adding the home point altitude
177                frame.osd.altitude = osd.altitude + frame.home.altitude;
178                frame.osd.height = osd.altitude;
179                frame.osd.vps_height = osd.s_wave_height;
180
181                frame.osd.x_speed = osd.speed_x;
182                frame.osd.y_speed = osd.speed_y;
183                frame.osd.z_speed = osd.speed_z;
184                frame.osd.pitch = osd.pitch;
185                frame.osd.yaw = osd.yaw;
186                frame.osd.roll = osd.roll;
187
188                if frame.osd.flyc_state != Some(osd.flight_mode) {
189                    frame.app.tip = append_message(
190                        frame.app.tip,
191                        format!("Flight mode changed to {:?}.", osd.flight_mode),
192                    );
193                }
194                frame.osd.flyc_state = Some(osd.flight_mode);
195                if let AppCommand::Unknown(0) = osd.app_command {
196                    frame.osd.flyc_command = None;
197                } else {
198                    frame.osd.flyc_command = Some(osd.app_command);
199                }
200                frame.osd.flight_action = Some(osd.flight_action);
201                frame.osd.gps_num = osd.gps_num;
202                frame.osd.gps_level = osd.gps_level;
203                frame.osd.is_gpd_used = osd.is_gps_valid;
204                frame.osd.non_gps_cause = Some(osd.non_gps_cause);
205                frame.osd.drone_type = Some(osd.drone_type);
206                frame.osd.is_swave_work = osd.is_swave_work;
207                frame.osd.wave_error = osd.wave_error;
208                frame.osd.go_home_status = Some(osd.go_home_status);
209                frame.osd.battery_type = Some(osd.battery_type);
210                frame.osd.is_on_ground = osd.ground_or_sky == GroundOrSky::Ground;
211                frame.osd.is_motor_on = osd.is_motor_up;
212                frame.osd.is_motor_blocked = osd.is_motor_blocked;
213                frame.osd.motor_start_failed_cause = Some(osd.motor_start_failed_cause);
214                frame.osd.is_imu_preheated = osd.is_imu_preheated;
215                frame.osd.imu_init_fail_reason = Some(osd.imu_init_fail_reason);
216                frame.osd.is_acceletor_over_range = osd.is_acceletor_over_range;
217                frame.osd.is_barometer_dead_in_air = osd.is_barometer_dead_in_air;
218                frame.osd.is_compass_error = osd.is_compass_error;
219                frame.osd.is_go_home_height_modified = osd.is_go_home_height_modified;
220                frame.osd.can_ioc_work = osd.can_ioc_work;
221                frame.osd.is_not_enough_force = osd.is_not_enough_force;
222                frame.osd.is_out_of_limit = osd.is_out_of_limit;
223                frame.osd.is_propeller_catapult = osd.is_propeller_catapult;
224                frame.osd.is_vibrating = osd.is_vibrating;
225                frame.osd.is_vision_used = osd.is_vision_used;
226                frame.osd.voltage_warning = osd.voltage_warning;
227
228                frame_index += 1;
229            }
230            Record::Gimbal(gimbal) => {
231                frame.gimbal.mode = Some(gimbal.mode);
232                frame.gimbal.pitch = gimbal.pitch;
233                frame.gimbal.roll = gimbal.roll;
234                frame.gimbal.yaw = gimbal.yaw;
235                if !frame.gimbal.is_pitch_at_limit && gimbal.is_pitch_at_limit {
236                    frame.app.tip =
237                        append_message(frame.app.tip, "Gimbal pitch axis endpoint reached.")
238                }
239                frame.gimbal.is_pitch_at_limit = gimbal.is_pitch_at_limit;
240                if !frame.gimbal.is_roll_at_limit && gimbal.is_roll_at_limit {
241                    frame.app.tip =
242                        append_message(frame.app.tip, "Gimbal roll axis endpoint reached.")
243                }
244                frame.gimbal.is_roll_at_limit = gimbal.is_roll_at_limit;
245                if !frame.gimbal.is_yaw_at_limit && gimbal.is_yaw_at_limit {
246                    frame.app.tip =
247                        append_message(frame.app.tip, "Gimbal yaw axis endpoint reached.")
248                }
249                frame.gimbal.is_yaw_at_limit = gimbal.is_yaw_at_limit;
250                frame.gimbal.is_stuck = gimbal.is_stuck;
251            }
252            Record::Camera(camera) => {
253                frame.camera.is_photo = camera.is_shooting_single_photo;
254                frame.camera.is_video = camera.is_recording;
255                frame.camera.sd_card_is_inserted = camera.has_sd_card;
256                frame.camera.sd_card_state = Some(camera.sd_card_state);
257            }
258            Record::RC(rc) => {
259                frame.rc.aileron = rc.aileron;
260                frame.rc.elevator = rc.elevator;
261                frame.rc.throttle = rc.throttle;
262                frame.rc.rudder = rc.rudder;
263            }
264            Record::RCDisplayField(rc) => {
265                frame.rc.aileron = rc.aileron;
266                frame.rc.elevator = rc.elevator;
267                frame.rc.throttle = rc.throttle;
268                frame.rc.rudder = rc.rudder;
269            }
270            Record::CenterBattery(battery) => {
271                frame.battery.charge_level = battery.relative_capacity;
272                frame.battery.voltage = battery.voltage;
273                frame.battery.current_capacity = battery.current_capacity as u32;
274                frame.battery.full_capacity = battery.full_capacity as u32;
275                frame.battery.full_capacity = battery.full_capacity as u32;
276                frame.battery.is_cell_voltage_estimated = false;
277
278                let cell_num = frame.battery.cell_voltages.len();
279                if cell_num > 0 {
280                    frame.battery.cell_voltages[0] = battery.voltage_cell1;
281                }
282                if cell_num > 1 {
283                    frame.battery.cell_voltages[1] = battery.voltage_cell2;
284                }
285                if cell_num > 2 {
286                    frame.battery.cell_voltages[2] = battery.voltage_cell3;
287                }
288                if cell_num > 3 {
289                    frame.battery.cell_voltages[3] = battery.voltage_cell4;
290                }
291                if cell_num > 4 {
292                    frame.battery.cell_voltages[4] = battery.voltage_cell5;
293                }
294                if cell_num > 5 {
295                    frame.battery.cell_voltages[5] = battery.voltage_cell6;
296                }
297            }
298            Record::SmartBattery(battery) => {
299                frame.battery.charge_level = battery.percent;
300                frame.battery.voltage = battery.voltage;
301            }
302            Record::SmartBatteryGroup(battery_group) => match battery_group {
303                SmartBatteryGroup::SmartBatteryStatic(_) => {}
304                SmartBatteryGroup::SmartBatteryDynamic(battery) => {
305                    // when there are multiple batteries, only one contains accurate values at index 1
306                    if details.product_type.battery_num() < 2 || battery.index == 1 {
307                        frame.battery.voltage = battery.current_voltage;
308                        frame.battery.current = battery.current_current;
309                        frame.battery.current_capacity = battery.remained_capacity;
310                        frame.battery.full_capacity = battery.full_capacity;
311                        frame.battery.charge_level = battery.capacity_percent;
312                        frame.battery.temperature = battery.temperature;
313                    }
314                }
315                SmartBatteryGroup::SmartBatterySingleVoltage(battery) => {
316                    let cell_num = frame
317                        .battery
318                        .cell_voltages
319                        .len()
320                        .min(battery.cell_count as usize);
321
322                    frame.battery.is_cell_voltage_estimated = false;
323
324                    frame.battery.cell_voltages[..cell_num]
325                        .copy_from_slice(&battery.cell_voltages[..cell_num]);
326                }
327            },
328            Record::OFDM(ofdm) => {
329                if ofdm.is_up {
330                    frame.rc.uplink_signal = Some(ofdm.signal_percent);
331                } else {
332                    frame.rc.downlink_signal = Some(ofdm.signal_percent);
333                }
334            }
335            Record::Custom(custom) => {
336                frame.custom.date_time = custom.update_timestamp;
337            }
338            Record::Home(home) => {
339                frame.home.latitude = home.latitude;
340                frame.home.longitude = home.longitude;
341                // If home_altitude was not previously set, also update osd.altitude
342                if frame.home.altitude == f32::default() {
343                    frame.osd.altitude += home.altitude;
344                }
345                frame.home.altitude = home.altitude;
346                frame.home.height_limit = home.max_allowed_height;
347                frame.home.is_home_record = home.is_home_record;
348                frame.home.go_home_mode = Some(home.go_home_mode);
349                frame.home.is_dynamic_home_point_enabled = home.is_dynamic_home_point_enabled;
350                frame.home.is_near_distance_limit = home.is_near_distance_limit;
351                frame.home.is_near_height_limit = home.is_near_height_limit;
352                frame.home.is_compass_calibrating = home.is_compass_adjust;
353                if home.is_compass_adjust {
354                    frame.home.compass_calibration_state = Some(home.compass_state);
355                }
356                frame.home.is_multiple_mode_enabled = home.is_multiple_mode_open;
357                frame.home.is_beginner_mode = home.is_beginner_mode;
358                frame.home.is_ioc_enabled = home.is_ioc_open;
359                if home.is_ioc_open {
360                    frame.home.ioc_mode = Some(home.ioc_mode);
361                }
362                frame.home.go_home_height = home.go_home_height;
363                if home.is_ioc_open {
364                    frame.home.ioc_course_lock_angle = Some(home.ioc_course_lock_angle);
365                }
366                frame.home.max_allowed_height = home.max_allowed_height;
367                frame.home.current_flight_record_index = home.current_flight_record_index;
368            }
369            Record::Recover(recover) => {
370                frame.recover.app_platform = Some(recover.app_platform);
371                frame.recover.app_version = recover.app_version;
372                frame.recover.aircraft_name = recover.aircraft_name;
373                frame.recover.aircraft_sn = recover.aircraft_sn;
374                frame.recover.camera_sn = recover.camera_sn;
375                frame.recover.rc_sn = recover.rc_sn;
376                frame.recover.battery_sn = recover.battery_sn;
377            }
378            Record::AppTip(app_tip) => {
379                frame.app.tip = append_message(frame.app.tip, app_tip.message);
380            }
381            Record::AppWarn(app_warn) => {
382                frame.app.warn = append_message(frame.app.warn, app_warn.message);
383            }
384            Record::AppSeriousWarn(app_serious_warn) => {
385                frame.app.warn = append_message(frame.app.warn, app_serious_warn.message);
386            }
387            _ => {}
388        }
389    }
390
391    frames
392}