Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 95 additions & 10 deletions Hitrava.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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')
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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')
Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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:
Expand Down