diff --git a/Hitrava.py b/Hitrava.py index bdf730e..52616aa 100644 --- a/Hitrava.py +++ b/Hitrava.py @@ -54,6 +54,7 @@ PROGRAM_PATCH_VERSION = '0' PROGRAM_MAJOR_BUILD = '2101' PROGRAM_MINOR_BUILD = '1801' +PROGRAM_DAN67_BUILD = '20210211_work' OUTPUT_DIR = './output' GPS_TIMEOUT = dts_delta(seconds=10) @@ -121,6 +122,13 @@ def __init__(self, activity_id: str, activity_type: str = TYPE_UNKNOWN): # Private variable to temporarily hold the last parsed SWOLF data during parsing of swimming activities self.last_swolf_data = None + # Private variable for JSON data + # for xml summary + self.JSON_total_time = 0 + self.JSON_total_distance = 0 + + + @classmethod def from_json_pool_swim_data(cls, activity_id: str, start: datetime, json_pool_swim_dict): """Create a HiActivity from the swim data in the JSON file. @@ -217,7 +225,7 @@ def _add_segment_stop(self, segment_stop: datetime, segment_distance: int = -1): self._current_segment = None - # TODO Verify if something useful can be done with the (optional) altitude data in the tp=lbs records + # Verify if something useful can be done with the (optional) altitude data in the tp=lbs records def add_location_data(self, data: []): """"Add location data from a tp=lbs record in the HiTrack file. Information: @@ -1140,23 +1148,25 @@ def extract_json(zip_filename: str, output_dir: str = OUTPUT_DIR, password: str class HiJson: # TODO find the correct values for the unknown/undocumented JSON sport types - _JSON_SPORT_TYPES = [(5, HiActivity.TYPE_WALK), - (4, HiActivity.TYPE_RUN), + # dan67 - sort by number + _JSON_SPORT_TYPES = [(2, HiActivity.TYPE_MOUNTAIN_HIKE), (3, HiActivity.TYPE_CYCLE), - (102, HiActivity.TYPE_POOL_SWIM), - (104, HiActivity.TYPE_OPEN_WATER_SWIM), - (282, HiActivity.TYPE_HIKE), - (2, HiActivity.TYPE_MOUNTAIN_HIKE), + (4, HiActivity.TYPE_RUN), + (5, HiActivity.TYPE_WALK), (101, HiActivity.TYPE_INDOOR_RUN), + (102, HiActivity.TYPE_POOL_SWIM), (103, HiActivity.TYPE_INDOOR_CYCLE), + (104, HiActivity.TYPE_OPEN_WATER_SWIM), (111, HiActivity.TYPE_CROSS_TRAINER), (117, HiActivity.TYPE_OTHER), + (118, HiActivity.TYPE_CROSS_COUNTRY_RUN), (145, HiActivity.TYPE_CROSSFIT), - (118, HiActivity.TYPE_CROSS_COUNTRY_RUN)] + (282, HiActivity.TYPE_HIKE) + ] _UNSUPPORTED_JSON_SPORT_TYPES = [] - def __init__(self, json_filename: str, output_dir: str = OUTPUT_DIR, export_json_data: bool = False): + def __init__(self, json_filename: str, output_dir: str = OUTPUT_DIR, export_json_data: bool = False, export_json_summary: bool = False): # Validate the JSON file parameter if not json_filename: logging.getLogger(PROGRAM_NAME).error('Parameter for JSON filename is missing') @@ -1174,6 +1184,8 @@ def __init__(self, json_filename: str, output_dir: str = OUTPUT_DIR, export_json self.export_json_data = export_json_data + self.export_json_summary = export_json_summary + self.hi_activity_list = [] def parse(self, from_date: datetime.date = datetime.date(1970, 1, 1)) -> list: @@ -1294,6 +1306,23 @@ def _parse_activity(self, activity_dict : dict) -> HiActivity: logging.getLogger(PROGRAM_NAME).error('Error closing JSON export file <%s>\n%s', json_filename, e) + if self.export_json_summary: + # Save aditional JSON data as summary of single activity. + json_summary_filename = hitrack_filename + '_summary.txt' + try: + json_summary = open(json_summary_filename, 'w+') + json_summary_data = self._generate_json_summary(activity_start,time_zone,activity_dict,activity_detail_dict) + for item in json_summary_data: + json_summary.write("%s\n" % item) + finally: + try: + if json_summary: + json_summary.close() + except Exception as e: + logging.getLogger(PROGRAM_NAME).error( + 'Error saving json summary', activity_start, e) + + logging.getLogger(PROGRAM_NAME).info( 'Saving activity from %s to HiTrack file %s for parsing', activity_start, hitrack_filename) try: @@ -1341,6 +1370,11 @@ def _parse_activity(self, activity_dict : dict) -> HiActivity: # For all activities except pool swimming, parse the HiTrack file hitrack_file = HiTrackFile(hitrack_filename) hi_activity = hitrack_file.parse() + + # write global info about training, for xml + hi_activity.JSON_total_distance = activity_dict["totalDistance"] + hi_activity.JSON_total_time = activity_dict["totalTime"]/1000 + if sport != HiActivity.TYPE_UNKNOWN: hi_activity.set_activity_type(sport) else: @@ -1387,6 +1421,40 @@ def _close_json(self): def __del__(self): self._close_json() + def _generate_json_summary(self, activity_start : str, time_zone : str, activity_dict : dict, activity_detail_dict : dict) -> list: + # generate summary info from original JSON data + + self.json_summary_list = [] + + self.json_summary_list.append("Date : " + _get_tz_aware_datetime(activity_start, time_zone).strftime('%Y%m%d_%H%M%S')) + self.json_summary_list.append("Activity : " + str(activity_dict["sportType"])) + # TODO add line with text sport type (I can not do it :-( ) + + self.json_summary_list.append("Duration : " + str(datetime.timedelta(seconds=activity_dict["totalTime"]/1000))) + self.json_summary_list.append("Distance : " + str(activity_dict["totalDistance"])) + self.json_summary_list.append("Calories : " + str(activity_dict["totalCalories"]/1000)) + self.json_summary_list.append("HR avg : " + str(activity_detail_dict["avgHeartRate"])) + self.json_summary_list.append("HR max : " + str(activity_detail_dict["maxHeartRate"])) + self.json_summary_list.append("Pace avg : " + str(datetime.timedelta(seconds=activity_detail_dict["avgPace"]))) + self.json_summary_list.append("Pace max : " + str(datetime.timedelta(seconds=activity_detail_dict["bestPace"]))) + if activity_detail_dict["avgPace"] != 0: + self.json_summary_list.append("Speed avg : " + str(round(3600 / activity_detail_dict["avgPace"], 2))) + else: + self.json_summary_list.append("Speed avg : ") + if activity_detail_dict["bestPace"] != 0: + self.json_summary_list.append("Speed max : " + str(round(3600 / activity_detail_dict["bestPace"], 2))) + else: + self.json_summary_list.append("Speed max : ") + self.json_summary_list.append("Steps avg : " + str(activity_detail_dict["avgStepRate"])) + self.json_summary_list.append("Steps max : " + str(activity_detail_dict["bestStepRate"])) + self.json_summary_list.append("Steps tot : " + str(activity_dict["totalSteps"])) + self.json_summary_list.append("Alt max : " + str(activity_detail_dict["mMaxAlti"])) + self.json_summary_list.append("Alt min : " + str(activity_detail_dict["mMinAlti"])) + self.json_summary_list.append("Ascend : " + str(activity_detail_dict["mTotalDescent"] / 10)) + self.json_summary_list.append("Descend : " + str(activity_detail_dict["creepingWave"] / 10)) + + return self.json_summary_list + class TcxActivity: # Strava accepts following sports: walking, running, biking, swimming. @@ -1488,6 +1556,7 @@ def generate_xml(self) -> xml_et.Element: # TODO verify if this is the case for Strava too or if something more meaningful can be passed. el_id = xml_et.SubElement(el_activity, 'Id') el_id.text = _get_tz_aware_datetime(self.hi_activity.start, self.hi_activity.time_zone).isoformat('T') + # Generate the activity xml content based on the type of activity if self.hi_activity.get_activity_type() in [HiActivity.TYPE_WALK, HiActivity.TYPE_RUN, @@ -1512,6 +1581,19 @@ def generate_xml(self) -> xml_et.Element: self.hi_activity.get_activity_type()) self._generate_walk_run_cycle_xml_data(el_activity) + + # *** Training + # *** genetare summary about training + + el_training = xml_et.SubElement(el_activity, 'Training') + el_training.set('VirtualPartner', 'false') + + el_QuickWorkoutResults = xml_et.SubElement(el_training, 'QuickWorkoutResults') + el_time = xml_et.SubElement(el_QuickWorkoutResults, 'TotalTimeSeconds') + el_time.text = str(self.hi_activity.JSON_total_time) + el_distance = xml_et.SubElement(el_QuickWorkoutResults, 'DistanceMeters') + el_distance.text = str(self.hi_activity.JSON_total_distance) + # *** Creator # TODO: verify if information is available in JSON file el_creator = xml_et.SubElement(el_activity, 'Creator') @@ -1900,6 +1982,9 @@ def _init_argument_parser() -> argparse.ArgumentParser: --json argument to e.g. run the conversion again for the JSON \ activity or for debugging purposes.', action='store_true') + json_group.add_argument('--json_summary', help='Use data from JSON file, without recalculation', + action='store_true') + file_group = parser.add_argument_group('FILE options') file_group.add_argument('-f', '--file', help='The filename of a single HiTrack file to convert.') file_group.add_argument('-s', '--sport', help='Force sport for the conversion. Sport will be auto-detected when \ @@ -2056,7 +2141,7 @@ def main(): json_filename = HiZip.extract_json(args.json, args.output_dir, args.password) else: json_filename = args.json - hi_json = HiJson(json_filename, args.output_dir, args.json_export) + hi_json = HiJson(json_filename, args.output_dir, args.json_export, args.json_summary) hi_activity_list = hi_json.parse(args.from_date) for n, hi_activity in enumerate(hi_activity_list, start=1): if args.pool_length: