@@ -3,6 +3,7 @@ mod bbl_format;
33use anyhow:: { Context , Result } ;
44use clap:: { Arg , Command } ;
55use glob:: glob;
6+ use semver:: Version ;
67use std:: collections:: HashMap ;
78use std:: io:: Write ;
89use std:: path:: Path ;
@@ -692,6 +693,7 @@ fn parse_headers_from_text(header_text: &str, debug: bool) -> Result<BBLHeader>
692693 // Parse P frame predictors
693694 if let Some ( pred_str) = line. strip_prefix ( "H Field P predictor:" ) {
694695 let predictors = parse_numeric_data ( pred_str) ;
696+
695697 // P frames inherit field names from I frames but have their own predictors
696698 if p_frame_def. field_names . is_empty ( ) && !i_frame_def. field_names . is_empty ( ) {
697699 p_frame_def =
@@ -740,6 +742,9 @@ fn parse_headers_from_text(header_text: &str, debug: bool) -> Result<BBLHeader>
740742 // Store numeric values that might be useful later
741743 if let Ok ( num_value) = field_value. parse :: < i32 > ( ) {
742744 sysconfig. insert ( field_name. to_string ( ) , num_value) ;
745+ if debug && field_name == "vbatref" {
746+ eprintln ! ( "DEBUG: Found vbatref={} in headers" , num_value) ;
747+ }
743748 }
744749 }
745750 }
@@ -753,6 +758,12 @@ fn parse_headers_from_text(header_text: &str, debug: bool) -> Result<BBLHeader>
753758 println ! ( "Data version: {data_version}, Looptime: {looptime}" ) ;
754759 }
755760
761+ // Extract vbatref from sysconfig for debug output
762+ let vbatref = sysconfig. get ( "vbatref" ) . copied ( ) . unwrap_or ( 0 ) ;
763+ if debug && vbatref > 0 {
764+ eprintln ! ( "DEBUG: Found vbatref={} in headers" , vbatref) ;
765+ }
766+
756767 Ok ( BBLHeader {
757768 firmware_revision,
758769 board_info,
@@ -1208,7 +1219,11 @@ fn export_flight_data_to_csv(log: &BBLLog, output_path: &Path, debug: bool) -> R
12081219 write ! ( writer, "{value:4}" ) ?;
12091220 } else if csv_name == "vbatLatest (V)" {
12101221 let raw_value = frame. data . get ( "vbatLatest" ) . copied ( ) . unwrap_or ( 0 ) ;
1211- write ! ( writer, "{:4.1}" , convert_vbat_to_volts( raw_value) ) ?;
1222+ write ! (
1223+ writer,
1224+ "{:4.1}" ,
1225+ convert_vbat_to_volts( raw_value, & log. header. firmware_revision)
1226+ ) ?;
12121227 } else if csv_name == "amperageLatest (A)" {
12131228 let raw_value = frame. data . get ( "amperageLatest" ) . copied ( ) . unwrap_or ( 0 ) ;
12141229 write ! ( writer, "{:4.2}" , convert_amperage_to_amps( raw_value) ) ?;
@@ -1376,6 +1391,7 @@ fn parse_frames(
13761391 false , // Not raw
13771392 header. data_version ,
13781393 & header. sysconfig ,
1394+ debug,
13791395 )
13801396 . is_ok ( )
13811397 {
@@ -1456,15 +1472,17 @@ fn parse_frames(
14561472 false , // Not raw
14571473 header. data_version ,
14581474 & header. sysconfig ,
1475+ debug,
14591476 )
14601477 . is_ok ( )
14611478 {
1462- // P-frames update only specific fields, rest inherit from previous I-frame
1479+ // P-frames: parse_frame_data already computed correct absolute values
1480+ // Copy previous frame as base, then update only P-frame fields
14631481 frame_history
14641482 . current_frame
14651483 . copy_from_slice ( & frame_history. previous_frame ) ;
14661484
1467- // Apply P-frame deltas to current frame
1485+ // Update only the fields that are present in P-frame with computed values
14681486 for ( i, field_name) in
14691487 header. p_frame_def . field_names . iter ( ) . enumerate ( )
14701488 {
@@ -1477,6 +1495,7 @@ fn parse_frames(
14771495 . position ( |name| name == field_name)
14781496 {
14791497 if i_frame_idx < frame_history. current_frame . len ( ) {
1498+ // p_frame_values[i] contains correctly calculated absolute value
14801499 frame_history. current_frame [ i_frame_idx] =
14811500 p_frame_values[ i] ;
14821501 }
@@ -1646,6 +1665,7 @@ fn parse_frames(
16461665 false , // Not raw
16471666 header. data_version ,
16481667 & header. sysconfig ,
1668+ debug,
16491669 )
16501670 . is_ok ( )
16511671 {
@@ -2268,14 +2288,56 @@ fn parse_numeric_data(numeric_data: &str) -> Vec<u8> {
22682288 . collect ( )
22692289}
22702290
2271- // Unit conversion functions
2272- fn convert_vbat_to_volts ( raw_value : i32 ) -> f32 {
2273- // Betaflight already does the ADC conversion to 0.1V units
2274- raw_value as f32 / 10.0
2291+ /// Converts raw vbatLatest value to volts using firmware-aware scaling.
2292+ ///
2293+ /// Betaflight < 4.3.0: tenths (0.1V units)
2294+ /// Betaflight >= 4.3.0: hundredths (0.01V units)
2295+ /// EmuFlight: always tenths (0.1V units)
2296+ /// iNav: always hundredths (0.01V units)
2297+ fn convert_vbat_to_volts ( raw_value : i32 , firmware_revision : & str ) -> f32 {
2298+ // Determine scaling factor based on firmware
2299+ let scale_factor = if firmware_revision. contains ( "EmuFlight" ) {
2300+ // EmuFlight always uses tenths
2301+ 0.1
2302+ } else if firmware_revision. contains ( "iNav" ) {
2303+ // iNav always uses hundredths
2304+ 0.01
2305+ } else if firmware_revision. contains ( "Betaflight" ) {
2306+ // Betaflight version-dependent scaling
2307+ if let Some ( version) = extract_firmware_version ( firmware_revision) {
2308+ if version >= Version :: new ( 4 , 3 , 0 ) {
2309+ 0.01 // hundredths for >= 4.3.0
2310+ } else {
2311+ 0.1 // tenths for < 4.3.0
2312+ }
2313+ } else {
2314+ // Default to modern Betaflight scaling if version can't be parsed
2315+ 0.01
2316+ }
2317+ } else {
2318+ // Unknown firmware, default to hundredths
2319+ 0.01
2320+ } ;
2321+
2322+ raw_value as f32 * scale_factor
2323+ }
2324+
2325+ /// Extract version from firmware revision string
2326+ fn extract_firmware_version ( firmware_revision : & str ) -> Option < Version > {
2327+ // Parse version from strings like "Betaflight 4.5.1 (77d01ba3b) AT32F435M"
2328+ let words: Vec < & str > = firmware_revision. split_whitespace ( ) . collect ( ) ;
2329+ for ( i, word) in words. iter ( ) . enumerate ( ) {
2330+ if word. to_lowercase ( ) . contains ( "betaflight" ) && i + 1 < words. len ( ) {
2331+ if let Ok ( version) = Version :: parse ( words[ i + 1 ] ) {
2332+ return Some ( version) ;
2333+ }
2334+ }
2335+ }
2336+ None
22752337}
22762338
2339+ /// Converts raw amperageLatest value to amps (0.01A units)
22772340fn convert_amperage_to_amps ( raw_value : i32 ) -> f32 {
2278- // Betaflight already does the ADC conversion to 0.01A units
22792341 raw_value as f32 / 100.0
22802342}
22812343
@@ -2695,9 +2757,23 @@ mod tests {
26952757
26962758 #[ test]
26972759 fn test_unit_conversions ( ) {
2698- // Test voltage conversion (0.1V units)
2699- let volts = convert_vbat_to_volts ( 33 ) ; // 33 * 0.1 = 3.3V
2700- assert ! ( ( volts - 3.3 ) . abs( ) < 0.01 ) ;
2760+ // Test voltage conversion with firmware-aware scaling
2761+
2762+ // Test Betaflight >= 4.3.0 (hundredths)
2763+ let volts_bf_new = convert_vbat_to_volts ( 1365 , "Betaflight 4.5.1 (77d01ba3b) AT32F435M" ) ;
2764+ assert ! ( ( volts_bf_new - 13.65 ) . abs( ) < 0.01 ) ; // Should be 13.65V (hundredths)
2765+
2766+ // Test Betaflight < 4.3.0 (tenths)
2767+ let volts_bf_old = convert_vbat_to_volts ( 136 , "Betaflight 4.2.0 (abc123) STM32F7X2" ) ;
2768+ assert ! ( ( volts_bf_old - 13.6 ) . abs( ) < 0.01 ) ; // Should be 13.6V (tenths)
2769+
2770+ // Test EmuFlight (always tenths)
2771+ let volts_emuf = convert_vbat_to_volts ( 136 , "EmuFlight 0.3.5 (abc123) STM32F7X2" ) ;
2772+ assert ! ( ( volts_emuf - 13.6 ) . abs( ) < 0.01 ) ; // Should be 13.6V (tenths)
2773+
2774+ // Test iNav (always hundredths)
2775+ let volts_inav = convert_vbat_to_volts ( 1365 , "iNav 7.1.0 (abc123) STM32F7X2" ) ;
2776+ assert ! ( ( volts_inav - 13.65 ) . abs( ) < 0.01 ) ; // Should be 13.65V (hundredths)
27012777
27022778 // Test amperage conversion (0.01A units)
27032779 let amps = convert_amperage_to_amps ( 100 ) ; // 100 * 0.01 = 1.0A
0 commit comments