#![allow(dead_code)]
use std::io::Result;
use std::io::Read;
use std::io::BufReader;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::io::{Error};
use std::convert::TryInto;
const HEADER_FILE_SIZE_OFFSET: usize = 0;
const HEADER_PROTOCOL_VERSION_OFFSET: usize = 1;
const HEADER_PROFILE_VERSION_LSB_OFFSET: usize = 2;
const HEADER_PROFILE_VERSION_MSB_OFFSET: usize = 3;
const HEADER_DATA_SIZE_LSB_OFFSET: usize = 4;
const HEADER_DATA_SIZE_1_OFFSET: usize = 5;
const HEADER_DATA_SIZE_2_OFFSET: usize = 6;
const HEADER_DATA_SIZE_MSB_OFFSET: usize = 7;
const HEADER_DATA_TYPE_0_OFFSET: usize = 8;
const HEADER_DATA_TYPE_1_OFFSET: usize = 9;
const HEADER_DATA_TYPE_2_OFFSET: usize = 10;
const HEADER_DATA_TYPE_3_OFFSET: usize = 11;
const HEADER_CRC_1_OFFSET: usize = 12;
const HEADER_CRC_2_OFFSET: usize = 13;
const DEF_MSG_RESERVED: usize = 0;
const DEF_MSG_ARCHITECTURE: usize = 1;
const DEF_MSG_GLOBAL_MSG_NUM: usize = 2;
const DEF_MSG_NUM_FIELDS: usize = 4;
const FIELD_MSG_INDEX: u8 = 254;
const FIELD_TIMESTAMP: u8 = 253;
const FIELD_PART_INDEX: u8 = 250;
const RECORD_HDR_NORMAL: u8 = 0x80;
const RECORD_HDR_MSG_TYPE: u8 = 0x40;
const RECORD_HDR_MSG_TYPE_SPECIFIC: u8 = 0x20;
const RECORD_HDR_RESERVED: u8 = 0x10;
const RECORD_HDR_LOCAL_MSG_TYPE: u8 = 0x0f;
const RECORD_HDR_LOCAL_MSG_TYPE_COMPRESSED: u8 = 0x60;
pub const GLOBAL_MSG_NUM_FILE_ID: u16 = 0;
pub const GLOBAL_MSG_NUM_CAPABILITIES: u16 = 1;
pub const GLOBAL_MSG_NUM_DEVICE_SETTINGS: u16 = 2;
pub const GLOBAL_MSG_NUM_USER_PROFILE: u16 = 3;
pub const GLOBAL_MSG_NUM_HRM_PROFILE: u16 = 4;
pub const GLOBAL_MSG_NUM_SDM_PROFILE: u16 = 5;
pub const GLOBAL_MSG_NUM_BIKE_PROFILE: u16 = 6;
pub const GLOBAL_MSG_NUM_ZONES_TARGET: u16 = 7;
pub const GLOBAL_MSG_NUM_HR_ZONE: u16 = 8;
pub const GLOBAL_MSG_NUM_POWER_ZONE: u16 = 9;
pub const GLOBAL_MSG_NUM_MET_ZONE: u16 = 10;
pub const GLOBAL_MSG_NUM_SPORT: u16 = 12;
pub const GLOBAL_MSG_NUM_GOAL: u16 = 15;
pub const GLOBAL_MSG_NUM_SESSION: u16 = 18;
pub const GLOBAL_MSG_NUM_LAP: u16 = 19;
pub const GLOBAL_MSG_NUM_RECORD: u16 = 20;
pub const GLOBAL_MSG_NUM_EVENT: u16 = 21;
pub const GLOBAL_MSG_NUM_DEVICE_INFO: u16 = 23;
pub const GLOBAL_MSG_NUM_WORKOUT: u16 = 26;
pub const GLOBAL_MSG_NUM_WORKOUT_STEP: u16 = 27;
pub const GLOBAL_MSG_NUM_SCHEDULE: u16 = 28;
pub const GLOBAL_MSG_NUM_WEIGHT_SCALE: u16 = 30;
pub const GLOBAL_MSG_NUM_COURSE: u16 = 31;
pub const GLOBAL_MSG_NUM_COURSE_POINT: u16 = 32;
pub const GLOBAL_MSG_NUM_TOTALS: u16 = 33;
pub const GLOBAL_MSG_NUM_ACTIVITY: u16 = 34;
pub const GLOBAL_MSG_NUM_SOFTWARE: u16 = 35;
pub const GLOBAL_MSG_NUM_FILE_CAPABILITIES: u16 = 37;
pub const GLOBAL_MSG_NUM_MESG_CAPABILITIES: u16 = 38;
pub const GLOBAL_MSG_NUM_FIELD_CAPABILITIES: u16 = 39;
pub const GLOBAL_MSG_NUM_FILE_CREATOR: u16 = 49;
pub const GLOBAL_MSG_NUM_BLOOD_PRESSURE: u16 = 51;
pub const GLOBAL_MSG_NUM_SPEED_ZONE: u16 = 53;
pub const GLOBAL_MSG_NUM_MONITORING: u16 = 55;
pub const GLOBAL_MSG_NUM_TRAINING_FILE: u16 = 72;
pub const GLOBAL_MSG_NUM_HRV: u16 = 78;
pub const GLOBAL_MSG_NUM_ANT_RX: u16 = 80;
pub const GLOBAL_MSG_NUM_ANT_TX: u16 = 81;
pub const GLOBAL_MSG_NUM_ANT_CHANNEL_ID: u16 = 82;
pub const GLOBAL_MSG_NUM_LENGTH: u16 = 101;
pub const GLOBAL_MSG_NUM_MONITORING_INFO: u16 = 103;
pub const GLOBAL_MSG_NUM_PAD: u16 = 105;
pub const GLOBAL_MSG_NUM_SLAVE_DEVICE: u16 = 106;
pub const GLOBAL_MSG_NUM_CONNECTIVITY: u16 = 127;
pub const GLOBAL_MSG_NUM_WEATHER_CONDITIONS: u16 = 128;
pub const GLOBAL_MSG_NUM_WEATHER_ALERT: u16 = 129;
pub const GLOBAL_MSG_NUM_CADENCE_ZONE: u16 = 131;
pub const GLOBAL_MSG_NUM_HR: u16 = 132;
pub const GLOBAL_MSG_NUM_SEGMENT_LAP: u16 = 142;
pub const GLOBAL_MSG_NUM_MEMO_GLOB: u16 = 145;
pub const GLOBAL_MSG_NUM_SEGMENT_ID: u16 = 148;
pub const GLOBAL_MSG_NUM_SEGMENT_LEADERBOARD_ENTRY: u16 = 149;
pub const GLOBAL_MSG_NUM_SEGMENT_POINT: u16 = 150;
pub const GLOBAL_MSG_NUM_SEGMENT_FILE: u16 = 151;
pub const GLOBAL_MSG_NUM_WORKOUT_SESSION: u16 = 158;
pub const GLOBAL_MSG_NUM_WATCHFACE_SETTINGS: u16 = 159;
pub const GLOBAL_MSG_NUM_GPS_METADATA: u16 = 160;
pub const GLOBAL_MSG_NUM_CAMERA_EVENT: u16 = 161;
pub const GLOBAL_MSG_NUM_TIMESTAMP_CORRELATION: u16 = 162;
pub const GLOBAL_MSG_NUM_GYROSCOPE_DATA: u16 = 164;
pub const GLOBAL_MSG_NUM_ACCELEROMETER_DATA: u16 = 165;
pub const GLOBAL_MSG_NUM_THREE_D_SENSOR_CALIBRATION: u16 = 167;
pub const GLOBAL_MSG_NUM_VIDEO_FRAME: u16 = 169;
pub const GLOBAL_MSG_NUM_OBDII_DATA: u16 = 174;
pub const GLOBAL_MSG_NUM_NMEA_SENTENCE: u16 = 177;
pub const GLOBAL_MSG_NUM_AVIATION_ATTITUDE: u16 = 178;
pub const GLOBAL_MSG_NUM_VIDEO: u16 = 184;
pub const GLOBAL_MSG_NUM_VIDEO_TITLE: u16 = 185;
pub const GLOBAL_MSG_NUM_VIDEO_DESCRIPTION: u16 = 186;
pub const GLOBAL_MSG_NUM_VIDEO_CLIP: u16 = 187;
pub const GLOBAL_MSG_NUM_OHR_SETTINGS: u16 = 188;
pub const GLOBAL_MSG_NUM_EXD_SCREEN_CONFIGURATION: u16 = 200;
pub const GLOBAL_MSG_NUM_EXD_DATA_FIELD_CONFIGURATION: u16 = 201;
pub const GLOBAL_MSG_NUM_EXD_DATA_CONCEPT_CONFIGURATION: u16 = 202;
pub const GLOBAL_MSG_NUM_FIELD_DESCRIPTION: u16 = 206;
pub const GLOBAL_MSG_NUM_DEVELOPER_DATA_ID: u16 = 207;
pub const GLOBAL_MSG_NUM_MAGNETOMETER_DATA: u16 = 208;
pub const GLOBAL_MSG_NUM_BAROMETER_DATA: u16 = 209;
pub const GLOBAL_MSG_NUM_ONE_D_SENSOR_CALIBRATION: u16 = 210;
pub const GLOBAL_MSG_NUM_SET: u16 = 225;
pub const GLOBAL_MSG_NUM_STRESS_LEVEL: u16 = 227;
pub const GLOBAL_MSG_NUM_DIVE_SETTINGS: u16 = 258;
pub const GLOBAL_MSG_NUM_DIVE_GAS: u16 = 259;
pub const GLOBAL_MSG_NUM_DIVE_ALARM: u16 = 262;
pub const GLOBAL_MSG_NUM_EXERCISE_TITLE: u16 = 264;
pub const GLOBAL_MSG_NUM_DIVE_SUMMARY: u16 = 268;
pub const GLOBAL_MSG_NUM_JUMP: u16 = 285;
pub const GLOBAL_MSG_NUM_CLIMB_PRO: u16 = 317;
pub const FIT_SPORT_GENERIC: u8 = 0;
pub const FIT_SPORT_RUNNING: u8 = 1;
pub const FIT_SPORT_CYCLING: u8 = 2;
pub const FIT_SPORT_TRANSITION: u8 = 3;
pub const FIT_SPORT_FITNESS_EQUIPMENT: u8 = 4;
pub const FIT_SPORT_SWIMMING: u8 = 5;
pub const FIT_SPORT_BASKETBALL: u8 = 6;
pub const FIT_SPORT_SOCCER: u8 = 7;
pub const FIT_SPORT_TENNIS: u8 = 8;
pub const FIT_SPORT_AMERICAN_FOOTBALL: u8 = 9;
pub const FIT_SPORT_TRAINING: u8 = 10;
pub const FIT_SPORT_WALKING: u8 = 11;
pub const FIT_SPORT_CROSS_COUNTRY_SKIING: u8 = 12;
pub const FIT_SPORT_ALPINE_SKIING: u8 = 13;
pub const FIT_SPORT_SNOWBOARDING: u8 = 14;
pub const FIT_SPORT_ROWING: u8 = 15;
pub const FIT_SPORT_MOUNTAINEERING: u8 = 16;
pub const FIT_SPORT_HIKING: u8 = 17;
pub const FIT_SPORT_MULTISPORT: u8 = 18;
pub const FIT_SPORT_PADDLING: u8 = 19;
pub const FIT_SPORT_FLYING: u8 = 20;
pub const FIT_SPORT_E_BIKING: u8 = 21;
pub const FIT_SPORT_MOTORCYCLING: u8 = 22;
pub const FIT_SPORT_BOATING: u8 = 23;
pub const FIT_SPORT_DRIVING: u8 = 24;
pub const FIT_SPORT_GOLF: u8 = 25;
pub const FIT_SPORT_HANG_GLIDING: u8 = 26;
pub const FIT_SPORT_HORSEBACK_RIDING: u8 = 27;
pub const FIT_SPORT_HUNTING: u8 = 28;
pub const FIT_SPORT_FISHING: u8 = 29;
pub const FIT_SPORT_INLINE_SKATING: u8 = 30;
pub const FIT_SPORT_ROCK_CLIMBING: u8 = 31;
pub const FIT_SPORT_SAILING: u8 = 32;
pub const FIT_SPORT_ICE_SKATING: u8 = 33;
pub const FIT_SPORT_SKY_DIVING: u8 = 34;
pub const FIT_SPORT_SNOWSHOEING: u8 = 35;
pub const FIT_SPORT_SNOWMOBILING: u8 = 36;
pub const FIT_SPORT_STAND_UP_PADDLEBOARDING: u8 = 37;
pub const FIT_SPORT_SURFING: u8 = 38;
pub const FIT_SPORT_WAKEBOARDING: u8 = 39;
pub const FIT_SPORT_WATER_SKIING: u8 = 40;
pub const FIT_SPORT_KAYAKING: u8 = 41;
pub const FIT_SPORT_RAFTING: u8 = 42;
pub const FIT_SPORT_WINDSURFING: u8 = 43;
pub const FIT_SPORT_KITESURFING: u8 = 44;
pub const FIT_SPORT_TACTICAL: u8 = 45;
pub const FIT_SPORT_JUMPMASTER: u8 = 46;
pub const FIT_SPORT_BOXING: u8 = 47;
pub const FIT_SPORT_FLOOR_CLIMBING: u8 = 48;
pub const FIT_SPORT_DIVING: u8 = 53;
pub const FIT_SPORT_ALL: u8 = 254;
pub const FIT_ENUM_INVALID: u8 = 0xff;
pub const FIT_STROKE_TYPE_INVALID: u8 = FIT_ENUM_INVALID;
pub const FIT_STROKE_TYPE_NO_EVENT: u8 = 0;
pub const FIT_STROKE_TYPE_OTHER: u8 = 1;
pub const FIT_STROKE_TYPE_SERVE: u8 = 2;
pub const FIT_STROKE_TYPE_FOREHAND: u8 = 3;
pub const FIT_STROKE_TYPE_BACKHAND: u8 = 4;
pub const FIT_STROKE_TYPE_SMASH: u8 = 5;
pub const FIT_STROKE_TYPE_COUNT: u8 = 6;
type Callback = fn(timestamp: u32, global_message_num: u16, local_message_type: u8, data: Vec<FieldValue>);
pub fn init_global_msg_name_map() -> HashMap<u16, String> {
let mut global_msg_name_map = HashMap::<u16, String>::new();
global_msg_name_map.insert(GLOBAL_MSG_NUM_FILE_ID, "File ID".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_CAPABILITIES, "Capabilities".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DEVICE_SETTINGS, "Device Settings".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_USER_PROFILE, "User Profile".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_HRM_PROFILE, "HRM Profile".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SDM_PROFILE, "SDM Profile".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_BIKE_PROFILE, "Bike Profile".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ZONES_TARGET, "Zones Target".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_HR_ZONE, "HR Zone".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_POWER_ZONE, "Power Zone".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_MET_ZONE, "MET Zone".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SPORT, "Sport".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_GOAL, "Goal".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SESSION, "Session".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_LAP, "Lap".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_RECORD, "Record".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_EVENT, "Event".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DEVICE_INFO, "Device Info".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WORKOUT, "Workout".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WORKOUT_STEP, "Workout Step".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SCHEDULE, "Schedule".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WEIGHT_SCALE, "Weight Scale".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_COURSE, "Course".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_COURSE_POINT, "Course Point".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_TOTALS, "Totals".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ACTIVITY, "Activity".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SOFTWARE, "Software".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_FILE_CAPABILITIES, "File Capabilities".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_MESG_CAPABILITIES, "Message Capabilities".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_FIELD_CAPABILITIES, "Field Capabilities".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_FILE_CREATOR, "File Creator".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_BLOOD_PRESSURE, "Blood Pressure".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SPEED_ZONE, "Speed Zone".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_MONITORING, "Monitoring".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_TRAINING_FILE, "Training File".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_HRV, "HRV".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ANT_RX, "ANT RX".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ANT_TX, "ANT TX".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ANT_CHANNEL_ID, "ANT Channel ID".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_LENGTH, "Length".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_MONITORING_INFO, "Monitoring Info".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_PAD, "Pad".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SLAVE_DEVICE, "Slave Device".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_CONNECTIVITY, "Connectivity".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WEATHER_CONDITIONS, "Weather".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WEATHER_ALERT, "Weather Alert".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_CADENCE_ZONE, "Cadence Zone".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_HR, "HR".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SEGMENT_LAP, "Segment Lap".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_MEMO_GLOB, "Memo Glob".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SEGMENT_ID, "Segment ID".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SEGMENT_LEADERBOARD_ENTRY, "Segment Leaderboard Entry".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SEGMENT_POINT, "Segment Point".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SEGMENT_FILE, "Segment File".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WORKOUT_SESSION, "Workout Session".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_WATCHFACE_SETTINGS, "Watch Face Settings".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_GPS_METADATA, "GPS".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_CAMERA_EVENT, "Camera Event".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_TIMESTAMP_CORRELATION, "Timestamp Correlation".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_GYROSCOPE_DATA, "Cyroscope Data".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ACCELEROMETER_DATA, "Accelerometer Data".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_THREE_D_SENSOR_CALIBRATION, "3D Sensor Calibration".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_VIDEO_FRAME, "Video Frame".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_OBDII_DATA, "OBDII Data".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_NMEA_SENTENCE, "NMEA Sentence".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_AVIATION_ATTITUDE, "Aviation Attitude".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_VIDEO, "Video".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_VIDEO_TITLE, "Video Title".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_VIDEO_DESCRIPTION, "Video Description".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_VIDEO_CLIP, "Video Clip".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_OHR_SETTINGS, "OHR Settings".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_EXD_SCREEN_CONFIGURATION, "EXD Screen Configuration".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_EXD_DATA_FIELD_CONFIGURATION, "EXD Data Field Configuration".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_EXD_DATA_CONCEPT_CONFIGURATION, "EXD Data Concept Configuration".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_FIELD_DESCRIPTION, "Field Description".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DEVELOPER_DATA_ID, "Developer Data ID".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_MAGNETOMETER_DATA, "Magnetometer Data".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_BAROMETER_DATA, "Barometer Data".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_ONE_D_SENSOR_CALIBRATION, "1D Sensor Calibration".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_SET, "Set".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_STRESS_LEVEL, "Stress Level".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DIVE_SETTINGS, "Dive Settings".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DIVE_GAS, "Dive Gas".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DIVE_ALARM, "Dive Alarm".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_EXERCISE_TITLE, "Exercise Title".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_DIVE_SUMMARY, "Dive Summary".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_JUMP, "Jump".to_string());
global_msg_name_map.insert(GLOBAL_MSG_NUM_CLIMB_PRO, "Climb Pro".to_string());
global_msg_name_map
}
pub fn init_sport_name_map() -> HashMap<u8, String> {
let mut sport_name_map = HashMap::<u8, String>::new();
sport_name_map.insert(FIT_SPORT_GENERIC, "Generic".to_string());
sport_name_map.insert(FIT_SPORT_RUNNING, "Running".to_string());
sport_name_map.insert(FIT_SPORT_CYCLING, "Cycling".to_string());
sport_name_map.insert(FIT_SPORT_TRANSITION, "Transition".to_string());
sport_name_map.insert(FIT_SPORT_FITNESS_EQUIPMENT, "Fitness Equipment".to_string());
sport_name_map.insert(FIT_SPORT_SWIMMING, "Swimming".to_string());
sport_name_map.insert(FIT_SPORT_BASKETBALL, "Basketball".to_string());
sport_name_map.insert(FIT_SPORT_SOCCER, "Soccer".to_string());
sport_name_map.insert(FIT_SPORT_TENNIS, "Tennis".to_string());
sport_name_map.insert(FIT_SPORT_AMERICAN_FOOTBALL, "American Football".to_string());
sport_name_map.insert(FIT_SPORT_TRAINING, "Training".to_string());
sport_name_map.insert(FIT_SPORT_WALKING, "Walking".to_string());
sport_name_map.insert(FIT_SPORT_CROSS_COUNTRY_SKIING, "Cross Country Skiing".to_string());
sport_name_map.insert(FIT_SPORT_ALPINE_SKIING, "Alpine Skiing".to_string());
sport_name_map.insert(FIT_SPORT_SNOWBOARDING, "Snowboarding".to_string());
sport_name_map.insert(FIT_SPORT_ROWING, "Rowing".to_string());
sport_name_map.insert(FIT_SPORT_MOUNTAINEERING, "Mountaineering".to_string());
sport_name_map.insert(FIT_SPORT_HIKING, "Hiking".to_string());
sport_name_map.insert(FIT_SPORT_MULTISPORT, "Multisport".to_string());
sport_name_map.insert(FIT_SPORT_PADDLING, "Paddling".to_string());
sport_name_map.insert(FIT_SPORT_FLYING, "Flying".to_string());
sport_name_map.insert(FIT_SPORT_E_BIKING, "E-Biking".to_string());
sport_name_map.insert(FIT_SPORT_MOTORCYCLING, "Motorcycling".to_string());
sport_name_map.insert(FIT_SPORT_BOATING, "Boating".to_string());
sport_name_map.insert(FIT_SPORT_DRIVING, "Driving".to_string());
sport_name_map.insert(FIT_SPORT_GOLF, "Golf".to_string());
sport_name_map.insert(FIT_SPORT_HANG_GLIDING, "Hang Gliding".to_string());
sport_name_map.insert(FIT_SPORT_HORSEBACK_RIDING, "Horseback Riding".to_string());
sport_name_map.insert(FIT_SPORT_HUNTING, "Hunting".to_string());
sport_name_map.insert(FIT_SPORT_FISHING, "Fishing".to_string());
sport_name_map.insert(FIT_SPORT_INLINE_SKATING, "Inline Skating".to_string());
sport_name_map.insert(FIT_SPORT_ROCK_CLIMBING, "Rock Climbing".to_string());
sport_name_map.insert(FIT_SPORT_SAILING, "Sailing".to_string());
sport_name_map.insert(FIT_SPORT_ICE_SKATING, "Ice Skating".to_string());
sport_name_map.insert(FIT_SPORT_SKY_DIVING, "Sky Diving".to_string());
sport_name_map.insert(FIT_SPORT_SNOWSHOEING, "Snowshoeing".to_string());
sport_name_map.insert(FIT_SPORT_SNOWMOBILING, "Snowmobiling".to_string());
sport_name_map.insert(FIT_SPORT_STAND_UP_PADDLEBOARDING, "Paddleboarding".to_string());
sport_name_map.insert(FIT_SPORT_SURFING, "Surfing".to_string());
sport_name_map.insert(FIT_SPORT_WAKEBOARDING, "Wakeboarding".to_string());
sport_name_map.insert(FIT_SPORT_WATER_SKIING, "Water Skiing".to_string());
sport_name_map.insert(FIT_SPORT_KAYAKING, "Kayaking".to_string());
sport_name_map.insert(FIT_SPORT_RAFTING, "Rafting".to_string());
sport_name_map.insert(FIT_SPORT_WINDSURFING, "Windsurfng".to_string());
sport_name_map.insert(FIT_SPORT_KITESURFING, "Kitesurfing".to_string());
sport_name_map.insert(FIT_SPORT_TACTICAL, "Tactical".to_string());
sport_name_map.insert(FIT_SPORT_JUMPMASTER, "Jumpmaster".to_string());
sport_name_map.insert(FIT_SPORT_BOXING, "Boxing".to_string());
sport_name_map.insert(FIT_SPORT_FLOOR_CLIMBING, "Floor Climbing".to_string());
sport_name_map.insert(FIT_SPORT_DIVING, "Diving".to_string());
sport_name_map.insert(FIT_SPORT_ALL, "All".to_string());
sport_name_map
}
fn read_n<R: Read>(reader: &mut BufReader<R>, bytes_to_read: u64) -> Result< Vec<u8> >
{
let mut buf = vec![];
let mut chunk = reader.take(bytes_to_read);
let _n = chunk.read_to_end(&mut buf).expect("Didn't read enough");
Ok(buf)
}
fn read_u32<R: Read>(reader: &mut BufReader<R>, is_big_endian: bool) -> Result<u32>
{
let bytes = read_n(reader, 4)?;
let num = byte_array_to_uint32(bytes, is_big_endian);
Ok(num)
}
fn read_byte<R: Read>(reader: &mut BufReader<R>) -> Result<u8>
{
let mut byte: [u8; 1] = [0; 1];
reader.read_exact(&mut byte)?;
Ok(byte[0])
}
fn read_string<R: Read>(reader: &mut BufReader<R>) -> Result<String>
{
let mut result = String::new();
let mut done = false;
while !done {
let buf = read_n(reader, 1)?;
if buf[0] == 0 {
done = true;
}
else {
result.push(buf[0] as char);
}
}
Ok(result)
}
fn byte_array_to_string(bytes: Vec<u8>, num_bytes: usize) -> String {
let mut result = String::new();
for i in 0..num_bytes {
result.push(bytes[i] as char);
}
result
}
fn byte_array_to_num(bytes: Vec<u8>, num_bytes: usize, is_big_endian: bool) -> u64 {
let mut num: u64 = 0;
if is_big_endian {
for i in 0..num_bytes {
num = (num << 8) | (bytes[i] as u64);
}
}
else {
for i in 0..num_bytes {
num = (num << 8) | (bytes[num_bytes - i - 1] as u64);
}
}
num
}
fn byte_array_to_uint64(bytes: Vec<u8>, is_big_endian: bool) -> u64 {
let temp = byte_array_to_num(bytes, 8, is_big_endian);
temp
}
fn byte_array_to_uint32(bytes: Vec<u8>, is_big_endian: bool) -> u32 {
let temp = byte_array_to_num(bytes, 4, is_big_endian) as u32;
temp
}
fn byte_array_to_uint16(bytes: Vec<u8>, is_big_endian: bool) -> u16 {
let temp = byte_array_to_num(bytes, 2, is_big_endian) as u16;
temp
}
fn byte_array_to_uint8(bytes: Vec<u8>) -> u8 {
bytes[0]
}
fn byte_array_to_sint64(bytes: Vec<u8>, is_big_endian: bool) -> i64 {
let temp = byte_array_to_num(bytes, 8, is_big_endian) as i64;
temp
}
fn byte_array_to_sint32(bytes: Vec<u8>, is_big_endian: bool) -> i32 {
let temp = byte_array_to_num(bytes, 4, is_big_endian) as i32;
temp
}
fn byte_array_to_sint16(bytes: Vec<u8>, is_big_endian: bool) -> i16 {
let temp = byte_array_to_num(bytes, 2, is_big_endian) as i16;
temp
}
fn byte_array_to_sint8(bytes: Vec<u8>) -> i8 {
let temp = bytes[0] as i8;
temp
}
fn byte_array_to_float(bytes: Vec<u8>, num_bytes: usize, _is_big_endian: bool) -> f64 {
if num_bytes == 1 {
return bytes[0] as f64;
}
else if num_bytes == 4 {
let byte_array = bytes.try_into().unwrap_or_else(|bytes: Vec<u8>| panic!("Expected a Vec of length {} but it was {}.", 4, bytes.len()));
return f32::from_bits(u32::from_be_bytes(byte_array)) as f64;
}
else if num_bytes == 8 {
let byte_array = bytes.try_into().unwrap_or_else(|bytes: Vec<u8>| panic!("Expected a Vec of length {} but it was {}.", 8, bytes.len()));
return f64::from_bits(u64::from_be_bytes(byte_array)) as f64;
}
0.0
}
pub fn semicircles_to_degrees(semicircles: i32) -> f64 {
let degrees = (semicircles as f64) * 0.000000083819032;
degrees
}
pub struct FitSessionMsg {
pub event: Option<u8>,
pub event_type: Option<u8>,
pub start_time: Option<u32>,
pub start_position_lat: Option<i32>,
pub start_position_long: Option<i32>,
pub sport: Option<u8>,
pub total_elapsed_time: Option<u32>,
pub total_timer_time: Option<u32>,
pub total_distance: Option<u32>,
pub total_cycles: Option<u32>,
pub total_calories: Option<u16>,
pub total_fat_calories: Option<u16>,
pub avg_speed: Option<u16>,
pub max_speed: Option<u16>,
pub avg_heart_rate: Option<u8>,
pub max_heart_rate: Option<u8>,
pub avg_cadence: Option<u8>,
pub max_cadence: Option<u8>,
pub avg_power: Option<u16>,
pub max_power: Option<u16>,
pub total_ascent: Option<u16>,
pub total_descent: Option<u16>,
pub total_training_effect: Option<u8>,
pub first_lap_index: Option<u16>,
pub num_laps: Option<u16>,
pub event_group: Option<u8>,
pub nec_lat: Option<i32>,
pub nec_long: Option<i32>,
pub swc_lat: Option<i32>,
pub swc_long: Option<i32>,
pub num_lengths: Option<u16>,
pub normalized_power: Option<u16>,
pub training_stress_score: Option<u16>,
pub intensity_factor: Option<u16>,
pub left_right_balance: Option<u16>,
pub avg_stroke_count: Option<u32>,
pub avg_stroke_distance: Option<u16>,
pub swim_stroke: Option<u8>,
pub pool_length: Option<u16>,
pub threshold_power: Option<u16>,
pub pool_length_unit: Option<u8>,
pub num_active_lengths: Option<u16>,
pub total_work: Option<u32>,
pub avg_altitude: Option<u16>,
pub max_altitude: Option<u16>,
pub gps_accuracy: Option<u8>,
pub avg_grade: Option<i16>,
pub avg_pos_grade: Option<i16>,
pub avg_neg_grade: Option<i16>,
pub max_pos_grade: Option<i16>,
pub max_neg_grade: Option<i16>,
pub avg_temperature: Option<i8>,
pub max_temperature: Option<i8>,
pub total_moving_time: Option<u16>,
pub avg_pos_vertical_speed: Option<u16>,
pub avg_neg_vertical_speed: Option<u16>,
pub max_pos_vertical_speed: Option<u16>,
pub max_neg_vertical_speed: Option<u16>,
pub min_heart_rate: Option<u8>,
pub time_in_hr_zone: Option<u32>,
pub time_in_speed_zone: Option<u32>,
pub time_in_cadence_zone: Option<u32>,
pub time_in_power_zone: Option<u32>,
pub avg_lap_time: Option<u32>,
pub best_lap_index: Option<u16>,
pub min_altitude: Option<u16>,
pub player_score: Option<u16>,
pub opponent_score: Option<u16>,
}
impl FitSessionMsg {
pub fn new(fields: Vec<FieldValue>) -> Self {
let mut msg = FitSessionMsg{ event: None, event_type: None, start_time: None, start_position_lat: None, start_position_long: None, sport: None,
total_elapsed_time: None, total_timer_time: None, total_distance: None, total_cycles: None, total_calories: None, total_fat_calories: None,
avg_speed: None, max_speed: None, avg_heart_rate: None, max_heart_rate: None, avg_cadence: None, max_cadence: None, avg_power: None, max_power: None,
total_ascent: None, total_descent: None, total_training_effect: None, first_lap_index: None, num_laps: None, event_group: None,
nec_lat: None, nec_long: None, swc_lat: None, swc_long: None, num_lengths: None, normalized_power: None, training_stress_score: None,
intensity_factor: None, left_right_balance: None, avg_stroke_count: None, avg_stroke_distance: None, swim_stroke: None, pool_length: None,
threshold_power: None, pool_length_unit: None, num_active_lengths: None, total_work: None, avg_altitude: None, max_altitude: None,
gps_accuracy: None, avg_grade: None, avg_pos_grade: None, avg_neg_grade: None, max_pos_grade: None, max_neg_grade: None,
avg_temperature: None, max_temperature: None, total_moving_time: None, avg_pos_vertical_speed: None, avg_neg_vertical_speed: None, max_pos_vertical_speed: None,
max_neg_vertical_speed: None, min_heart_rate: None, time_in_hr_zone: None, time_in_speed_zone: None, time_in_cadence_zone: None,
time_in_power_zone: None, avg_lap_time: None, best_lap_index: None, min_altitude: None, player_score: None, opponent_score: None };
for field in fields {
match field.field_def {
0 => { msg.event = Some(field.get_u8()); },
1 => { msg.event_type = Some(field.get_u8()); },
2 => { msg.start_time = Some(field.get_u32()); },
3 => { msg.start_position_lat = Some(field.get_i32()); },
4 => { msg.start_position_long = Some(field.get_i32()); },
5 => { msg.sport = Some(field.get_u8()); },
7 => { msg.total_elapsed_time = Some(field.get_u32()); },
8 => { msg.total_timer_time = Some(field.get_u32()); },
9 => { msg.total_distance = Some(field.get_u32()); },
10 => { msg.total_cycles = Some(field.get_u32()); },
11 => { msg.total_calories = Some(field.get_u16()); },
13 => { msg.total_fat_calories = Some(field.get_u16()); },
14 => { msg.avg_speed = Some(field.get_u16()); },
15 => { msg.max_speed = Some(field.get_u16()); },
16 => { msg.avg_heart_rate = Some(field.get_u8()); },
17 => { msg.max_heart_rate = Some(field.get_u8()); },
18 => { msg.avg_cadence = Some(field.get_u8()); },
19 => { msg.max_cadence = Some(field.get_u8()); },
20 => { msg.avg_power = Some(field.get_u16()); },
21 => { msg.max_power = Some(field.get_u16()); },
22 => { msg.total_ascent = Some(field.get_u16()); },
23 => { msg.total_descent = Some(field.get_u16()); },
24 => { msg.total_training_effect = Some(field.get_u8()); },
25 => { msg.first_lap_index = Some(field.get_u16()); },
26 => { msg.num_laps = Some(field.get_u16()); },
27 => { msg.event_group = Some(field.get_u8()); },
29 => { msg.nec_lat = Some(field.get_i32()); },
30 => { msg.nec_long = Some(field.get_i32()); },
31 => { msg.swc_lat = Some(field.get_i32()); },
32 => { msg.swc_long = Some(field.get_i32()); },
33 => { msg.num_lengths = Some(field.get_u16()); },
34 => { msg.normalized_power = Some(field.get_u16()); },
35 => { msg.training_stress_score = Some(field.get_u16()); },
36 => { msg.intensity_factor = Some(field.get_u16()); },
37 => { msg.left_right_balance = Some(field.get_u16()); },
41 => { msg.avg_stroke_count = Some(field.get_u32()); },
42 => { msg.avg_stroke_distance = Some(field.get_u16()); },
43 => { msg.swim_stroke = Some(field.get_u8()); },
44 => { msg.pool_length = Some(field.get_u16()); },
45 => { msg.threshold_power = Some(field.get_u16()); },
46 => { msg.pool_length_unit = Some(field.get_u8()); },
47 => { msg.num_active_lengths = Some(field.get_u16()); },
48 => { msg.total_work = Some(field.get_u32()); },
49 => { msg.avg_altitude = Some(field.get_u16()); },
50 => { msg.max_altitude = Some(field.get_u16()); },
51 => { msg.gps_accuracy = Some(field.get_u8()); },
52 => { msg.avg_grade = Some(field.get_i16()); },
53 => { msg.avg_pos_grade = Some(field.get_i16()); },
54 => { msg.avg_neg_grade = Some(field.get_i16()); },
55 => { msg.max_pos_grade = Some(field.get_i16()); },
56 => { msg.max_neg_grade = Some(field.get_i16()); },
57 => { msg.avg_temperature = Some(field.get_i8()); },
58 => { msg.max_temperature = Some(field.get_i8()); },
59 => { msg.total_moving_time = Some(field.get_u16()); },
60 => { msg.avg_pos_vertical_speed = Some(field.get_u16()); },
61 => { msg.avg_neg_vertical_speed = Some(field.get_u16()); },
62 => { msg.max_pos_vertical_speed = Some(field.get_u16()); },
63 => { msg.max_neg_vertical_speed = Some(field.get_u16()); },
64 => { msg.min_heart_rate = Some(field.get_u8()); },
65 => { msg.time_in_hr_zone = Some(field.get_u32()); },
66 => { msg.time_in_speed_zone = Some(field.get_u32()); },
67 => { msg.time_in_cadence_zone = Some(field.get_u32()); },
68 => { msg.time_in_power_zone = Some(field.get_u32()); },
69 => { msg.avg_lap_time = Some(field.get_u32()); },
70 => { msg.best_lap_index = Some(field.get_u16()); },
71 => { msg.min_altitude = Some(field.get_u16()); },
82 => { msg.player_score = Some(field.get_u16()); },
83 => { msg.opponent_score = Some(field.get_u16()); },
_ => { panic!("Session field not implemented {:#x}", field.field_def); }
}
}
msg
}
}
pub struct FitDeviceInfoMsg {
pub timestamp: Option<u32>,
pub serial_number: Option<u32>,
pub cum_operating_time: Option<u32>,
pub product_name: Option<String>,
pub manufacturer: Option<u16>,
pub product: Option<u16>,
pub software_version: Option<u16>,
pub battery_voltage: Option<u16>,
pub ant_device_number: Option<u16>,
pub device_index: Option<u8>,
pub device_type: Option<u8>,
pub hardware_version: Option<u8>,
pub battery_status: Option<u8>,
pub descriptor: Option<String>,
pub ant_transmission_type: Option<u8>,
pub source_type: Option<u8>
}
impl FitDeviceInfoMsg {
pub fn new(fields: Vec<FieldValue>) -> Self {
let mut msg = FitDeviceInfoMsg{ timestamp: None, serial_number: None, cum_operating_time: None, product_name: None, manufacturer: None,
product: None, software_version: None, battery_voltage: None, ant_device_number: None, device_index: None, device_type: None,
battery_status: None, hardware_version: None, descriptor: None, ant_transmission_type: None, source_type: None };
for field in fields {
match field.field_def {
0 => { msg.device_index = Some(field.get_u8()); },
1 => { msg.device_type = Some(field.get_u8()); },
2 => { msg.manufacturer = Some(field.get_u16()); },
3 => { msg.serial_number = Some(field.get_u32()); },
4 => { msg.product = Some(field.get_u16()); },
5 => { msg.software_version = Some(field.get_u16()); },
6 => { msg.hardware_version = Some(field.get_u8()); },
7 => { msg.cum_operating_time = Some(field.get_u32()); },
10 => { msg.battery_voltage = Some(field.get_u16()); },
11 => { msg.battery_status = Some(field.get_u8()); },
16 => { msg.ant_device_number = Some(field.get_u16()); },
19 => { msg.descriptor = Some(field.string); },
21 => { msg.ant_device_number = Some(field.get_u16()); },
25 => { msg.source_type = Some(field.get_u8()); },
27 => { msg.product_name = Some(field.string); },
_ => { panic!("Device Info field not implemented {:#x}", field.field_def); }
}
}
msg
}
}
pub struct FitRecordMsg {
pub timestamp: Option<u32>,
pub position_lat: Option<i32>,
pub position_long: Option<i32>,
pub distance: Option<u32>,
pub time_from_course: Option<i32>,
pub total_cycles: Option<u32>,
pub accumulated_power: Option<u32>,
pub enhanced_speed: Option<u32>,
pub enhanced_altitude: Option<u32>,
pub altitude: Option<u16>,
pub speed: Option<u16>,
pub power: Option<u16>,
pub grade: Option<i16>,
pub compressed_accumulated_power: Option<u16>,
pub vertical_speed: Option<i16>,
pub calories: Option<u16>,
pub vertical_oscillation: Option<u16>,
pub stance_time_percent: Option<u16>,
pub stance_time: Option<u16>,
pub ball_speed: Option<u16>,
pub cadence256: Option<u16>,
pub total_hemoglobin_conc: Option<u16>,
pub total_hemoglobin_conc_min: Option<u16>,
pub total_hemoglobin_conc_max: Option<u16>,
pub saturated_hemoglobin_percent: Option<u16>,
pub saturated_hemoglobin_percent_min: Option<u16>,
pub saturated_hemoglobin_percent_max: Option<u16>,
pub heart_rate: Option<u8>,
pub cadence: Option<u8>,
pub resistance: Option<u8>,
pub cycle_length: Option<u8>,
pub temperature: Option<i8>,
pub cycles: Option<u8>,
pub left_right_balance: Option<u8>,
pub gps_accuracy: Option<u8>,
pub activity_type: Option<u8>,
pub left_torque_effectiveness: Option<u8>,
pub right_torque_effectiveness: Option<u8>,
pub left_pedal_smoothness: Option<u8>,
pub right_pedal_smoothness: Option<u8>,
pub combined_pedal_smoothness: Option<u8>,
pub time128: Option<u8>,
pub stroke_type: Option<u8>,
pub zone: Option<u8>,
pub fractional_cadence: Option<u8>,
pub battery_soc: Option<u8>
}
impl FitRecordMsg {
pub fn new(fields: Vec<FieldValue>) -> Self {
let mut msg = FitRecordMsg{ timestamp: None, position_lat: None, position_long: None, distance: None, time_from_course: None, total_cycles: None, accumulated_power: None,
enhanced_speed: None, enhanced_altitude: None, altitude: None, speed: None, power: None, grade: None, compressed_accumulated_power: None, vertical_speed: None,
calories: None, vertical_oscillation: None, stance_time_percent: None, stance_time: None, ball_speed: None, cadence256: None, total_hemoglobin_conc: None,
total_hemoglobin_conc_min: None, total_hemoglobin_conc_max: None, saturated_hemoglobin_percent: None, saturated_hemoglobin_percent_min: None,
saturated_hemoglobin_percent_max: None, heart_rate: None, cadence: None, resistance: None, cycle_length: None, temperature: None,
cycles: None, left_right_balance: None, gps_accuracy: None, activity_type: None, left_torque_effectiveness: None, right_torque_effectiveness: None,
left_pedal_smoothness: None, right_pedal_smoothness: None, combined_pedal_smoothness: None, time128: None, stroke_type: None, zone: None, fractional_cadence: None,
battery_soc: None };
for field in fields {
match field.field_def {
0 => { msg.position_lat = Some(field.get_i32()); },
1 => { msg.position_long = Some(field.get_i32()); },
2 => { msg.altitude = Some(field.get_u16()); },
3 => { msg.heart_rate = Some(field.get_u8()); },
4 => { msg.cadence = Some(field.get_u8()); },
5 => { msg.distance = Some(field.get_u32()); },
6 => { msg.speed = Some(field.get_u16()); },
7 => { msg.power = Some(field.get_u16()); },
9 => { msg.grade = Some(field.get_i16()); },
13 => { msg.temperature = Some(field.get_i8()); },
31 => { msg.gps_accuracy = Some(field.get_u8()); },
43 => { msg.left_torque_effectiveness = Some(field.get_u8()); },
44 => { msg.right_torque_effectiveness = Some(field.get_u8()); },
45 => { msg.left_pedal_smoothness = Some(field.get_u8()); },
46 => { msg.right_pedal_smoothness = Some(field.get_u8()); },
81 => { msg.battery_soc = Some(field.get_u8()); },
_ => { panic!("Record field not implemented {:#x}", field.field_def); }
}
}
msg
}
}
pub enum FieldType {
FieldTypeNotSet,
FieldTypeUInt,
FieldTypeSInt,
FieldTypeFloat,
FieldTypeByteArray,
FieldTypeStr
}
pub struct FieldValue {
pub field_def: u8,
pub field_type: FieldType,
pub num_uint: u64,
pub num_sint: i64,
pub num_float: f64,
pub byte_array: Vec<u8>,
pub string: String
}
impl FieldValue {
pub fn new() -> Self {
let state = FieldValue{ field_def: 0, field_type: FieldType::FieldTypeNotSet, num_uint: 0, num_sint: 0, num_float: 0.0, byte_array: Vec::<u8>::new(), string: String::new() };
state
}
pub fn get_i8(&self) -> i8 {
return self.num_sint as i8;
}
pub fn get_i16(&self) -> i16 {
return self.num_sint as i16;
}
pub fn get_i32(&self) -> i32 {
return self.num_sint as i32;
}
pub fn get_i64(&self) -> i64 {
return self.num_sint as i64;
}
pub fn get_u8(&self) -> u8 {
return self.num_uint as u8;
}
pub fn get_u16(&self) -> u16 {
return self.num_uint as u16;
}
pub fn get_u32(&self) -> u32 {
return self.num_uint as u32;
}
pub fn get_u64(&self) -> u64 {
return self.num_uint as u64;
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct FieldDefinition {
pub field_def: u8,
pub size: u8,
pub base_type: u8
}
impl Ord for FieldDefinition {
fn cmp(&self, other: &Self) -> Ordering {
(self.field_def, &self.size, &self.base_type).cmp(&(other.field_def, &other.size, &other.base_type))
}
}
impl PartialOrd for FieldDefinition {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for FieldDefinition {
fn eq(&self, other: &Self) -> bool {
(self.field_def, &self.size) == (other.field_def, &other.size)
}
}
impl Eq for FieldDefinition { }
pub type FieldDefinitionList = Vec<FieldDefinition>;
#[derive(Debug, Default)]
struct GlobalMessage {
local_msg_defs: HashMap<u8, FieldDefinitionList>,
endianness_map: HashMap<u8, bool>,
}
impl GlobalMessage {
pub fn new() -> Self {
let msg = GlobalMessage{ local_msg_defs: HashMap::<u8, FieldDefinitionList>::new(), endianness_map: HashMap::<u8, bool>::new() };
msg
}
fn print(&self) {
for (local_msg_type, local_msg_def) in &self.local_msg_defs {
println!(" Local Message Type {}:", local_msg_type);
for field_definition in local_msg_def {
println!(" Field Num {}: Size {} Base Type {:#x}", field_definition.field_def, field_definition.size, field_definition.base_type);
}
}
}
fn insert_msg_def(&mut self, local_msg_type: u8, local_msg_def: FieldDefinitionList, is_big_endian: bool) {
if self.local_msg_defs.contains_key(&local_msg_type) {
self.local_msg_defs.remove(&local_msg_type);
}
if self.endianness_map.contains_key(&local_msg_type) {
self.endianness_map.remove(&local_msg_type);
}
self.local_msg_defs.insert(local_msg_type, local_msg_def);
self.endianness_map.insert(local_msg_type, is_big_endian);
}
fn retrieve_msg_def(&self, local_msg_type: u8) -> ( Option<&FieldDefinitionList>, Option<&bool> ) {
return ( self.local_msg_defs.get(&local_msg_type), self.endianness_map.get(&local_msg_type) )
}
}
#[derive(Debug, Default)]
struct FitState {
current_global_msg_num: u16,
global_msg_map: HashMap<u16, GlobalMessage>,
timestamp: u32
}
impl FitState {
pub fn new() -> Self {
let state = FitState{ current_global_msg_num: 0, global_msg_map: HashMap::<u16, GlobalMessage>::new(), timestamp: 0 };
state
}
fn print(&self) {
println!("----------------------------------------");
println!("Current Global Message Number: {}", self.current_global_msg_num);
for (global_msg_num, global_msg_def) in &self.global_msg_map {
println!("Global Message Number {}:", global_msg_num);
global_msg_def.print();
}
}
fn insert_global_msg(&mut self, global_msg_num: u16) {
if !self.global_msg_map.contains_key(&global_msg_num) {
self.global_msg_map.insert(global_msg_num, GlobalMessage::new());
}
}
fn insert_local_msg_def(&mut self, global_msg_num: u16, local_msg_type: u8, local_msg_def: FieldDefinitionList, is_big_endian: bool) {
self.global_msg_map.entry(global_msg_num)
.and_modify(|e| { e.insert_msg_def(local_msg_type, local_msg_def, is_big_endian) })
.or_insert(GlobalMessage::new());
}
fn retrieve_msg_def(&self, global_msg_num: u16, local_msg_type: u8) -> ( Option<&FieldDefinitionList>, Option<&bool> ) {
let global_msg_def = self.global_msg_map.get(&global_msg_num);
match global_msg_def {
Some(global_msg_def) => {
return global_msg_def.retrieve_msg_def(local_msg_type);
}
None => {
return ( None, None );
}
}
}
}
#[derive(Debug, Default)]
pub struct FitHeader {
pub header: Vec<u8>,
pub header_buf2: [u8; 2]
}
impl FitHeader {
pub fn new() -> Self {
let header = FitHeader{ header: Vec::new(), header_buf2: [0u8; 2] };
header
}
pub fn read<R: Read>(&mut self, reader: &mut BufReader<R>) -> Result<()> {
self.header = read_n(reader, 12)?;
if self.header[HEADER_FILE_SIZE_OFFSET] == 14 {
let mut additional_bytes = read_n(reader, 2)?;
self.header.append(&mut additional_bytes);
}
Ok(())
}
pub fn validate(&self) -> bool {
let mut valid = self.header[HEADER_DATA_TYPE_0_OFFSET] == '.' as u8;
valid = valid && self.header[HEADER_DATA_TYPE_1_OFFSET] == 'F' as u8;
valid = valid && self.header[HEADER_DATA_TYPE_2_OFFSET] == 'I' as u8;
valid = valid && self.header[HEADER_DATA_TYPE_3_OFFSET] == 'T' as u8;
valid
}
pub fn print(&self) {
for byte in self.header.iter() {
print!("{:#04x} ", byte);
}
}
pub fn data_size(&self) -> u32 {
let mut data_size = self.header[HEADER_DATA_SIZE_LSB_OFFSET] as u32;
data_size = data_size | (self.header[HEADER_DATA_SIZE_1_OFFSET] as u32) << 8;
data_size = data_size | (self.header[HEADER_DATA_SIZE_2_OFFSET] as u32) << 16;
data_size = data_size | (self.header[HEADER_DATA_SIZE_MSB_OFFSET] as u32) << 24;
data_size
}
}
#[derive(Debug, Default)]
struct FitRecord {
}
impl FitRecord {
pub fn new() -> Self {
let rec = FitRecord{ };
rec
}
fn read_definition_message<R: Read>(&mut self, reader: &mut BufReader<R>, header_byte: u8, state: &mut FitState) -> Result<()> {
let local_msg_type = header_byte & RECORD_HDR_LOCAL_MSG_TYPE;
let mut definition_header: [u8; 5] = [0; 5];
reader.read_exact(&mut definition_header)?;
let is_big_endian = definition_header[DEF_MSG_ARCHITECTURE] == 1;
let global_msg_num = byte_array_to_uint16(definition_header[DEF_MSG_GLOBAL_MSG_NUM..(DEF_MSG_GLOBAL_MSG_NUM + 2)].to_vec(), is_big_endian);
state.current_global_msg_num = global_msg_num;
state.insert_global_msg(global_msg_num);
let mut field_defs: FieldDefinitionList = FieldDefinitionList::new();
let num_fields = definition_header[DEF_MSG_NUM_FIELDS];
for _i in 0..num_fields {
let field_num = read_byte(reader)?;
let field_bytes = read_byte(reader)?;
let field_type = read_byte(reader)?;
let field_def = FieldDefinition { field_def:field_num, size:field_bytes, base_type:field_type };
field_defs.push(field_def);
}
if header_byte & RECORD_HDR_MSG_TYPE_SPECIFIC != 0 {
let num_dev_fields = read_byte(reader)?;
for _i in 0..num_dev_fields {
let field_num = read_byte(reader)?;
let field_bytes = read_byte(reader)?;
let field_type = read_byte(reader)?;
let field_def = FieldDefinition { field_def:field_num, size:field_bytes, base_type:field_type };
field_defs.push(field_def);
}
}
state.insert_local_msg_def(global_msg_num, local_msg_type, field_defs, is_big_endian);
Ok(())
}
fn read_data_message<R: Read>(&mut self, reader: &mut BufReader<R>, header_byte: u8, state: &mut FitState, callback: Callback) -> Result<()> {
let local_msg_type;
if header_byte & RECORD_HDR_NORMAL != 0 {
local_msg_type = (header_byte & RECORD_HDR_LOCAL_MSG_TYPE_COMPRESSED) >> 5;
}
else {
local_msg_type = header_byte & RECORD_HDR_LOCAL_MSG_TYPE;
}
let mut new_timestamp = state.timestamp;
let ( field_defs, is_big_endian_ref ) = state.retrieve_msg_def(state.current_global_msg_num, local_msg_type);
match is_big_endian_ref {
Some(is_big_endian_ref) => {
let is_big_endian = *is_big_endian_ref;
match field_defs {
Some(field_defs) => {
let mut fields = Vec::new();
for def in field_defs.iter() {
let mut field = FieldValue::new();
field.field_def = def.field_def;
let data = read_n(reader, def.size as u64)?;
if def.field_def == FIELD_MSG_INDEX {
panic!("Message Index not implemented: global message num: {} local message type: {}.", state.current_global_msg_num, local_msg_type);
}
else if def.field_def == FIELD_TIMESTAMP {
new_timestamp = byte_array_to_uint32(data, is_big_endian);
}
else if def.field_def == FIELD_PART_INDEX {
panic!("Part Index not implemented: global message num: {} local message type: {}.", state.current_global_msg_num, local_msg_type);
}
else {
match def.base_type {
0x00 => { field.num_uint = byte_array_to_uint8(data) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x01 => { field.num_sint = byte_array_to_sint8(data) as i64; field.field_type = FieldType::FieldTypeSInt; },
0x02 => { field.num_uint = byte_array_to_uint8(data) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x83 => { field.num_sint = byte_array_to_sint16(data, is_big_endian) as i64; field.field_type = FieldType::FieldTypeSInt; },
0x84 => { field.num_uint = byte_array_to_uint16(data, is_big_endian) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x85 => { field.num_sint = byte_array_to_sint32(data, is_big_endian) as i64; field.field_type = FieldType::FieldTypeSInt; },
0x86 => { field.num_uint = byte_array_to_uint32(data, is_big_endian) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x07 => { field.string = byte_array_to_string(data, def.size as usize); field.field_type = FieldType::FieldTypeStr; },
0x88 => { field.num_float = byte_array_to_float(data, 4, is_big_endian); field.field_type = FieldType::FieldTypeFloat; },
0x89 => { field.num_float = byte_array_to_float(data, 8, is_big_endian); field.field_type = FieldType::FieldTypeFloat; },
0x0A => { field.num_uint = byte_array_to_uint8(data) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x8B => { field.num_uint = byte_array_to_uint16(data, is_big_endian) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x8C => { field.num_uint = byte_array_to_uint32(data, is_big_endian) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x0D => { field.byte_array = data; field.field_type = FieldType::FieldTypeByteArray; },
0x8E => { field.num_sint = byte_array_to_sint64(data, is_big_endian) as i64; field.field_type = FieldType::FieldTypeSInt; },
0x8F => { field.num_uint = byte_array_to_uint64(data, is_big_endian) as u64; field.field_type = FieldType::FieldTypeUInt; },
0x90 => { field.num_uint = byte_array_to_uint64(data, is_big_endian) as u64; field.field_type = FieldType::FieldTypeUInt; },
_ => { panic!("Base Type not implemented {:#x}", def.base_type); }
}
fields.push(field);
}
}
callback(631065600 + state.timestamp, state.current_global_msg_num, local_msg_type, fields);
},
None => {
let e = Error::new(std::io::ErrorKind::Other, "Message definition not found.");
return Err(e);
},
}
}
None => {
let e = Error::new(std::io::ErrorKind::Other, "Message endianness not found.");
return Err(e);
},
}
state.timestamp = new_timestamp;
Ok(())
}
fn read_compressed_timestamp_message<R: Read>(&mut self, reader: &mut BufReader<R>, header_byte: u8, state: &mut FitState, callback: Callback) -> Result<()> {
let time_offset = (header_byte & 0x1f) as u32;
if time_offset >= state.timestamp & 0x0000001F {
state.timestamp = (state.timestamp & 0xFFFFFFE0) + time_offset;
}
else {
state.timestamp = (state.timestamp & 0xFFFFFFE0) + time_offset + 0x00000020;
}
self.read_data_message(reader, header_byte, state, callback)?;
Ok(())
}
fn read_normal_message<R: Read>(&mut self, reader: &mut BufReader<R>, header_byte: u8, state: &mut FitState, callback: Callback) -> Result<()> {
if header_byte & RECORD_HDR_RESERVED != 0 {
panic!("Reserve bit set");
}
if header_byte & RECORD_HDR_MSG_TYPE != 0 {
self.read_definition_message(reader, header_byte, state)?;
}
else {
self.read_data_message(reader, header_byte, state, callback)?;
}
Ok(())
}
fn read<R: Read>(&mut self, reader: &mut BufReader<R>, state: &mut FitState, callback: Callback) -> Result<()> {
let header_byte = read_byte(reader)?;
if header_byte & RECORD_HDR_NORMAL != 0 {
self.read_compressed_timestamp_message(reader, header_byte, state, callback)?;
}
else {
self.read_normal_message(reader, header_byte, state, callback)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct Fit {
pub header: FitHeader
}
impl Fit {
pub fn new() -> Self {
let fit = Fit{ header: FitHeader::new() };
fit
}
fn check_crc(&self, crc: u16, byte: u8) {
let crc_table: [u16; 16] = [
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
];
let mut crc2 = crc;
let mut tmp: u16 = crc_table[(crc2 & 0xf) as usize];
crc2 = (crc2 >> 4) & 0x0fff;
crc2 = crc2 ^ tmp ^ crc_table[(byte & 0xf) as usize];
tmp = crc_table[(crc2 & 0xf) as usize];
crc2 = (crc2 >> 4) & 0x0fff;
crc2 = crc2 ^ tmp ^ crc_table[((byte >> 4) & 0xf) as usize];
}
pub fn read<R: Read>(&mut self, reader: &mut BufReader<R>, callback: Callback) -> Result<()> {
self.header.read(reader)?;
if self.header.validate() {
let mut state = FitState::new();
while !reader.buffer().is_empty() {
let mut record = FitRecord::new();
record.read(reader, &mut state, callback)?;
}
}
Ok(())
}
}
pub fn read<R: Read>(reader: &mut BufReader<R>, callback: Callback) -> Result<Fit> {
let mut fit: Fit = Fit::new();
fit.read(reader, callback)?;
Ok(fit)
}