def DownloadActivity(self, svcRecord, activity): userID = svcRecord.ExternalID activity_id = activity.ServiceData["ActivityID"] logging.info("\t\t FITBIT LOADING : " + str(activity_id)) activity_tcx_uri = 'https://api.fitbit.com/1/user/' + userID + '/activities/' + str(activity_id) + '.tcx' resp = self._requestWithAuth(lambda session: session.get( activity_tcx_uri, headers={ 'Authorization': 'Bearer ' + svcRecord.Authorization.get('AccessToken') }), svcRecord) # check if request has error if resp.status_code != 204 and resp.status_code != 200: raise APIException("Unable to find Fitbit TCX activity") # Prepare tcxio params ns = copy.deepcopy(TCXIO.Namespaces) ns["tcx"] = ns[None] del ns[None] # Read tcx to know if this is a stationary activity or not try: root = etree.XML(resp.text.encode('utf-8')) except: root = etree.fromstring(resp.text.encode('utf-8')) xacts = root.find("tcx:Activities", namespaces=ns) if xacts is None: raise ValueError("No activities element in TCX") xact = xacts.find("tcx:Activity", namespaces=ns) if xact is None: raise ValueError("No activity element in TCX") # Define activity type from tcx if not activity.Type or activity.Type == ActivityType.Other: if xact.attrib["Sport"] == "Biking": activity.Type = ActivityType.Cycling elif xact.attrib["Sport"] == "Running": activity.Type = ActivityType.Running # Find all lap in tcx xlaps = xact.findall("tcx:Lap", namespaces=ns) if len(xlaps) > 0: activity = TCXIO.Parse(resp.text.encode('utf-8'), activity) else: # Define lap for activity lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] lap.Waypoints = [] activity.GPS = False activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, svcRecord, activity): if activity.ServiceData["Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats)] return activity activityID = activity.ServiceData["ActivityID"] streamdata = requests.get("https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng,distance,velocity_smooth", headers=self._apiHeaders(svcRecord)) if streamdata.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata["message"] == "Record Not Found": raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] if "error" in ridedata: raise APIException("Strava error " + ridedata["error"]) activity.Laps = [] res = requests.get("https://www.strava.com/api/v3/activities/{}".format(activityID), headers=self._apiHeaders(svcRecord)) activityDetails = res.json() activity.Notes = activityDetails["description"] lapsdata = activityDetails["laps"] for lapdata in lapsdata: lapWaypoints, lapStats = self._process_lap_waypoints(activity, ridedata, lapdata) lapStart = pytz.utc.localize(datetime.strptime(lapdata["start_date"], "%Y-%m-%dT%H:%M:%SZ")) lapEnd = lapStart + timedelta(0, lapdata["elapsed_time"]) lap = Lap(startTime=lapStart, endTime=lapEnd) lap.Waypoints = lapWaypoints # In single-lap case lap stats needs to match global stats lap.Stats = activity.Stats if len(lapsdata) == 1 else lapStats activity.Laps.append(lap) return activity
def DownloadActivity(self, serviceRecord, activity): activityID = activity.ServiceData["ActivityID"] logger.debug("DownloadActivity %s" % activityID) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] lap.Waypoints = [] response = requests.get("https://api.mapmyfitness.com/v7.1/workout/" + activityID + "/?field_set=time_series", headers=self._apiHeaders(serviceRecord)) data = response.json() activity.GPS = False activity.Stationary = True # add waypoints to laps if "time_series" in data and "position" in data["time_series"]: activity.Stationary = False for pt in data["time_series"]["position"]: timestamp = pt[0] wp = Waypoint(activity.StartTime + timedelta(seconds=round(timestamp))) pos = pt[1] if ("lat" in pos and "lng" in pos) or "elevation" in pos: wp.Location = Location() if "lat" in pos and "lng" in pos: wp.Location.Latitude = pos["lat"] wp.Location.Longitude = pos["lng"] activity.GPS = True if "elevation" in pos: wp.Location.Altitude = pos["elevation"] lap.Waypoints.append(wp) return activity
def DownloadActivity(self, svcRecord, activity): if activity.ServiceData[ "Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [ Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats) ] return activity activityID = activity.ServiceData["ActivityID"] self._globalRateLimit() streamdata = requests.get( "https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng,distance,velocity_smooth", headers=self._apiHeaders(svcRecord)) if streamdata.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata[ "message"] == "Record Not Found": raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] lap = Lap( stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime ) # Strava doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] hasHR = "heartrate" in ridedata and len(ridedata["heartrate"]) > 0 hasCadence = "cadence" in ridedata and len(ridedata["cadence"]) > 0 hasTemp = "temp" in ridedata and len(ridedata["temp"]) > 0 hasPower = ("watts" in ridedata and len(ridedata["watts"]) > 0) hasAltitude = "altitude" in ridedata and len(ridedata["altitude"]) > 0 hasDistance = "distance" in ridedata and len(ridedata["distance"]) > 0 hasVelocity = "velocity_smooth" in ridedata and len( ridedata["velocity_smooth"]) > 0 if "error" in ridedata: raise APIException("Strava error " + ridedata["error"]) inPause = False waypointCt = len(ridedata["time"]) for idx in range(0, waypointCt - 1): waypoint = Waypoint(activity.StartTime + timedelta(0, ridedata["time"][idx])) if "latlng" in ridedata: latlng = ridedata["latlng"][idx] waypoint.Location = Location(latlng[0], latlng[1], None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None if hasAltitude: if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = float(ridedata["altitude"][idx]) # When pausing, Strava sends this format: # idx = 100 ; time = 1000; moving = true # idx = 101 ; time = 1001; moving = true => convert to Pause # idx = 102 ; time = 2001; moving = false => convert to Resume: (2001-1001) seconds pause # idx = 103 ; time = 2002; moving = true if idx == 0: waypoint.Type = WaypointType.Start elif idx == waypointCt - 2: waypoint.Type = WaypointType.End elif idx < waypointCt - 2 and ridedata["moving"][idx + 1] and inPause: waypoint.Type = WaypointType.Resume inPause = False elif idx < waypointCt - 2 and not ridedata["moving"][ idx + 1] and not inPause: waypoint.Type = WaypointType.Pause inPause = True if hasHR: waypoint.HR = ridedata["heartrate"][idx] if hasCadence: waypoint.Cadence = ridedata["cadence"][idx] if hasTemp: waypoint.Temp = ridedata["temp"][idx] if hasPower: waypoint.Power = ridedata["watts"][idx] if hasVelocity: waypoint.Speed = ridedata["velocity_smooth"][idx] if hasDistance: waypoint.Distance = ridedata["distance"][idx] lap.Waypoints.append(waypoint) return activity
def DownloadActivity(self, svcRecord, activity): if activity.ServiceData["Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats)] return activity activityID = activity.ServiceData["ActivityID"] self._globalRateLimit() streamdata = requests.get("https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng", headers=self._apiHeaders(svcRecord)) if streamdata.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata["message"] == "Record Not Found": raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) # Strava doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] hasHR = "heartrate" in ridedata and len(ridedata["heartrate"]) > 0 hasCadence = "cadence" in ridedata and len(ridedata["cadence"]) > 0 hasTemp = "temp" in ridedata and len(ridedata["temp"]) > 0 hasPower = ("watts" in ridedata and len(ridedata["watts"]) > 0) hasAltitude = "altitude" in ridedata and len(ridedata["altitude"]) > 0 if "error" in ridedata: raise APIException("Strava error " + ridedata["error"]) waypointCt = len(ridedata["time"]) for idx in range(0, waypointCt - 1): waypoint = Waypoint(activity.StartTime + timedelta(0, ridedata["time"][idx])) if "latlng" in ridedata: latlng = ridedata["latlng"][idx] waypoint.Location = Location(latlng[0], latlng[1], None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None if hasAltitude: if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = float(ridedata["altitude"][idx]) if idx == 0: waypoint.Type = WaypointType.Start elif idx == waypointCt - 2: waypoint.Type = WaypointType.End if hasHR: waypoint.HR = ridedata["heartrate"][idx] if hasCadence: waypoint.Cadence = ridedata["cadence"][idx] if hasTemp: waypoint.Temp = ridedata["temp"][idx] if hasPower: waypoint.Power = ridedata["watts"][idx] lap.Waypoints.append(waypoint) return activity
def DownloadActivity(self, svcRecord, activity): activityID = activity.ServiceData["ActivityID"] extID = svcRecord.ExternalID url = self.SingletrackerDomain + "getRideData" payload = {"userId": extID, "rideId": activityID} headers = { 'content-type': "application/json", 'cache-control': "no-cache", } streamdata = requests.post(url, data=json.dumps(payload), headers=headers) if streamdata.status_code == 500: raise APIException("Internal server error") if streamdata.status_code == 403: raise APIException("No authorization to download activity", block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) if streamdata.status_code == 200: # Ok try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") ridedata = {} lap = Lap( stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime ) # Singletracker doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] wayPointExist = False for stream in streamdata: waypoint = Waypoint( dateutil.parser.parse(stream["time"], ignoretz=True)) if "latitude" in stream: if "longitude" in stream: latitude = stream["latitude"] longitude = stream["longitude"] waypoint.Location = Location(latitude, longitude, None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None if "elevation" in stream: if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = stream["elevation"] if "distance" in stream: waypoint.Distance = stream["distance"] if "speed" in stream: waypoint.Speed = stream["speed"] waypoint.Type = WaypointType.Regular lap.Waypoints.append(waypoint) return activity
def DownloadActivity(self, svcRecord, activity): service_id = svcRecord._id user = db.users.find_one({ 'ConnectedServices': { '$elemMatch': { 'ID': service_id, 'Service': self.ID } } }) userID = svcRecord.ExternalID oauth_token = svcRecord.Authorization.get('OAuthToken') user_access_token = svcRecord.Authorization.get('AccessToken') user_access_token_secret = svcRecord.Authorization.get( 'AccessTokenSecret') logging.info("\t Building signin for activity detail") user_tokens = { 'access_token': user_access_token, 'access_token_secret': user_access_token_secret, 'oauth_token': oauth_token } payload = "" start_date = datetime.now() - timedelta(days=1) end_date = start_date + timedelta(seconds=86400) start_date_tmstmp = str(int(start_date.timestamp())) end_date_tmstmp = str(int(end_date.timestamp())) start_date_str = start_date.strftime("%Y-%m-%d") end_date_str = end_date.strftime("%Y-%m-%d") signin_parameters = { 'upload_start_time': start_date_tmstmp, 'upload_end_time': end_date_tmstmp, } signin_info = self._request_signin('GET', self.URI_ACTIVITIES_DETAIL, user_tokens, parameters=signin_parameters) resp = requests.request("GET", signin_info['path'], data=payload, headers=signin_info['header']) if resp.status_code != 204 and resp.status_code != 200: logging.info( "\t An error occured while downloading Garmin Health activities from %s to %s " % (start_date_str, end_date_str)) json_data = resp.json() activity_id = activity.ServiceData["ActivityID"] activity_detail_id = activity_id + '-detail' if json_data: for item in json_data: if activity_detail_id == item['summaryId']: lapsdata = [] if "laps" in item: for lap in item['laps']: lapsdata.append(lap['startTimeInSeconds']) ridedata = {} lapWaypoints = [] startTimeLap = activity.StartTime endTimeLap = activity.EndTime if "samples" in item: activity.GPS = True activity.Stationary = False for pt in item['samples']: wp = Waypoint() delta = int(pt.get('clockDurationInSeconds')) dateStartPoint = int(pt.get('startTimeInSeconds')) dateStartPointDt = datetime.utcfromtimestamp( dateStartPoint) wp.Timestamp = dateStartPointDt wp.Location = Location() if "latitudeInDegree" in pt: wp.Location.Latitude = float( pt.get('latitudeInDegree')) if "longitudeInDegree" in pt: wp.Location.Longitude = float( pt.get('longitudeInDegree')) if "elevationInMeters" in pt: wp.Location.Altitude = int( pt.get('elevationInMeters')) if "totalDistanceInMeters" in pt: wp.Distance = int( pt.get('totalDistanceInMeters')) if "speedMetersPerSecond" in pt: wp.Speed = int(pt.get('speedMetersPerSecond')) if "heartRate" in pt: wp.HR = int(pt.get('heartRate')) # current sample is = to lap occur , so store current nap and build a new one if dateStartPoint in lapsdata: lap = Lap(stats=activity.Stats, startTime=startTimeLap, endTime=dateStartPointDt) lap.Waypoints = lapWaypoints activity.Laps.append(lap) # re init a new lap startTimeLap = datetime.utcfromtimestamp( dateStartPoint) lapWaypoints = [] # add occur lapWaypoints.append(wp) # build last lap if len(lapWaypoints) > 0: lap = Lap(stats=activity.Stats, startTime=startTimeLap, endTime=endTimeLap) lap.Waypoints = lapWaypoints activity.Laps.append(lap) else: activity.Laps = [ Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats) ] break return activity
def DownloadActivity(self, svcRecord, activity): activityID = activity.ServiceData["ActivityID"] logger.info("\t\t DC LOADING : " + str(activityID)) headers = self._getAuthHeaders(svcRecord) resp = requests.get(self.ApiEndpoint + "/activity/" + activityID + "/fullactivity.xml", headers=headers) if resp.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) try: root = xml.fromstring(resp.content) except: raise APIException( "Stream data returned from DecathlonCoach is not XML") lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] lap.Waypoints = [] activity.GPS = False #work on date startdate = root.find('.//STARTDATE').text timezone = root.find('.//TIMEZONE').text datebase = parse(startdate + timezone) for pt in root.iter('LOCATION'): wp = Waypoint() delta = int(pt.get('elapsed_time')) formatedDate = datebase + timedelta(seconds=delta) wp.Timestamp = formatedDate #self._parseDate(formatedDate.isoformat()) wp.Location = Location() wp.Location.Latitude = float(pt.find('LATITUDE').text[:8]) wp.Location.Longitude = float(pt.find('LONGITUDE').text[:8]) activity.GPS = True wp.Location.Altitude = int(pt.find('ELEVATION').text[:8]) #get the HR value in the Datastream node and measures collection for hr in root.iter('MEASURE'): if pt.get('elapsed_time') == hr.get('elapsed_time'): for measureValue in hr.iter('VALUE'): if measureValue.get('id') == "1": wp.HR = int(measureValue.text) break break lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, svcRecord, activity): activityID = activity.ServiceData["ActivityID"] logging.info("\t\t DC LOADING : " + str(activityID)) headers = self._getAuthHeaders(svcRecord) self._rate_limit() resp = requests.get(DECATHLON_API_BASE_URL + "/activity/" + activityID + "/fullactivity.xml", headers=headers) if resp.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) try: root = xml.fromstring(resp.content) except: raise APIException( "Stream data returned from Decathlon is not XML") activity.GPS = False activity.Stationary = True #work on date startdate = root.find('.//STARTDATE').text timezone = root.find('.//TIMEZONE').text datebase = parse(startdate + timezone) ridedata = {} ridedataindex = [] for pt in root.iter('LOCATION'): delta = int(pt.get('elapsed_time')) ridedataindex.append(delta) ridedata[delta] = {} if activityID == 'eu2132ac60d9a40a1d9a': logging.info('========time : ' + str(delta)) logging.info('========lat : ' + str(float(pt.find('LATITUDE').text[:8]))) ridedata[delta]['LATITUDE'] = float(pt.find('LATITUDE').text[:8]) ridedata[delta]['LONGITUDE'] = float(pt.find('LONGITUDE').text[:8]) ridedata[delta]['ELEVATION'] = int(pt.find('ELEVATION').text[:8]) if len(ridedata) > 0: activity.GPS = True activity.Stationary = False for measure in root.iter('MEASURE'): delta = int(measure.get('elapsed_time')) if delta not in ridedataindex: ridedataindex.append(delta) ridedata[delta] = {} for measureValue in measure.iter('VALUE'): if measureValue.get('id') == "1": ridedata[delta]['HR'] = int(measureValue.text) if measureValue.get('id') == "6": ridedata[delta]['SPEED'] = int(measureValue.text) if measureValue.get('id') == "5": ridedata[delta]['DISTANCE'] = int(measureValue.text) if measureValue.get('id') == "20": ridedata[delta]['LAP'] = int(measureValue.text) ridedataindex.sort() if len(ridedata) == 0: lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] else: lapWaypoints = [] startTimeLap = activity.StartTime for elapsedTime in ridedataindex: rd = ridedata[elapsedTime] wp = Waypoint() delta = elapsedTime formatedDate = datebase + timedelta(seconds=delta) wp.Timestamp = formatedDate #self._parseDate(formatedDate.isoformat()) if 'LATITUDE' in rd: wp.Location = Location() wp.Location.Latitude = rd['LATITUDE'] wp.Location.Longitude = rd['LONGITUDE'] wp.Location.Altitude = rd['ELEVATION'] if 'HR' in rd: wp.HR = rd['HR'] if 'SPEED' in rd: wp.Speed = rd['SPEED'] / 3600 if 'DISTANCE' in rd: wp.Distance = rd['DISTANCE'] lapWaypoints.append(wp) if "LAP" in rd: #build the lap lap = Lap(stats=activity.Stats, startTime=startTimeLap, endTime=formatedDate) lap.Waypoints = lapWaypoints activity.Laps.append(lap) # re init a new lap startTimeLap = formatedDate lapWaypoints = [] #build last lap if len(lapWaypoints) > 0: lap = Lap(stats=activity.Stats, startTime=startTimeLap, endTime=formatedDate) lap.Waypoints = lapWaypoints activity.Laps.append(lap) return activity
def DownloadActivity(self, svcRecord, activity): if activity.ServiceData["Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats)] return activity activityID = activity.ServiceData["ActivityID"] streamdata = requests.get("https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng", headers=self._apiHeaders(svcRecord)) if streamdata.status_code == 401: self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "auth") raise APIException("No authorization to download activity", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata["message"] == "Record Not Found": self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "missing") raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) # Strava doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] hasHR = "heartrate" in ridedata and len(ridedata["heartrate"]) > 0 hasCadence = "cadence" in ridedata and len(ridedata["cadence"]) > 0 hasTemp = "temp" in ridedata and len(ridedata["temp"]) > 0 hasPower = ("watts" in ridedata and len(ridedata["watts"]) > 0) hasAltitude = "altitude" in ridedata and len(ridedata["altitude"]) > 0 hasMovingData = "moving" in ridedata and len(ridedata["moving"]) > 0 moving = True if "error" in ridedata: self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "data") raise APIException("Strava error " + ridedata["error"]) hasLocation = False waypointCt = len(ridedata["time"]) for idx in range(0, waypointCt - 1): latlng = ridedata["latlng"][idx] waypoint = Waypoint(activity.StartTime + timedelta(0, ridedata["time"][idx])) latlng = ridedata["latlng"][idx] waypoint.Location = Location(latlng[0], latlng[1], None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None else: # strava only returns 0 as invalid coords, so no need to check for null (update: ??) hasLocation = True if hasAltitude: waypoint.Location.Altitude = float(ridedata["altitude"][idx]) if idx == 0: waypoint.Type = WaypointType.Start elif idx == waypointCt - 2: waypoint.Type = WaypointType.End elif hasMovingData and not moving and ridedata["moving"][idx] is True: waypoint.Type = WaypointType.Resume moving = True elif hasMovingData and ridedata["moving"][idx] is False: waypoint.Type = WaypointType.Pause moving = False if hasHR: waypoint.HR = ridedata["heartrate"][idx] if hasCadence: waypoint.Cadence = ridedata["cadence"][idx] if hasTemp: waypoint.Temp = ridedata["temp"][idx] if hasPower: waypoint.Power = ridedata["watts"][idx] lap.Waypoints.append(waypoint) if not hasLocation: self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "faulty") raise APIExcludeActivity("No waypoints with location", activityId=activityID, userException=UserException(UserExceptionType.Corrupt)) self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), None) return activity
def DownloadActivity(self, svcRecord, activity): activityID = activity.ServiceData["ActivityID"] extID = svcRecord.ExternalID url = self.SingletrackerDomain + "getRideData" payload = {"userId": extID, "rideId": activityID} headers = { 'content-type': "application/json", 'cache-control': "no-cache", } streamdata = requests.post(url, data=json.dumps(payload), headers=headers) if streamdata.status_code == 500: raise APIException("Internal server error") if streamdata.status_code == 403: raise APIException("No authorization to download activity", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) if streamdata.status_code != 200: raise APIException("Unknown Singletracker response %d %s" % (streamdata.status_code, streamdata.text)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") ridedata = {} lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) # Singletracker doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] wayPointExist = False for stream in streamdata: waypoint = Waypoint(dateutil.parser.parse(stream["time"], ignoretz=True)) if "latitude" in stream: if "longitude" in stream: latitude = stream["latitude"] longitude = stream["longitude"] waypoint.Location = Location(latitude, longitude, None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None if "elevation" in stream: if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = stream["elevation"] if "distance" in stream: waypoint.Distance = stream["distance"] if "speed" in stream: waypoint.Speed = stream["speed"] waypoint.Type = WaypointType.Regular lap.Waypoints.append(waypoint) return activity
def DownloadActivity(self, svcRecord, activity): if activity.ServiceData[ "Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [ Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats) ] return activity activityID = activity.ServiceData["ActivityID"] streamdata = requests.get( "https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng", headers=self._apiHeaders(svcRecord)) if streamdata.status_code == 401: self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "auth") raise APIException("No authorization to download activity", block=True, user_exception=UserException( UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata[ "message"] == "Record Not Found": self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "missing") raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] lap = Lap( stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime ) # Strava doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] hasHR = "heartrate" in ridedata and len(ridedata["heartrate"]) > 0 hasCadence = "cadence" in ridedata and len(ridedata["cadence"]) > 0 hasTemp = "temp" in ridedata and len(ridedata["temp"]) > 0 hasPower = ("watts" in ridedata and len(ridedata["watts"]) > 0) hasAltitude = "altitude" in ridedata and len(ridedata["altitude"]) > 0 hasMovingData = "moving" in ridedata and len(ridedata["moving"]) > 0 moving = True if "error" in ridedata: self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "data") raise APIException("Strava error " + ridedata["error"]) hasLocation = False waypointCt = len(ridedata["time"]) for idx in range(0, waypointCt - 1): latlng = ridedata["latlng"][idx] waypoint = Waypoint(activity.StartTime + timedelta(0, ridedata["time"][idx])) latlng = ridedata["latlng"][idx] waypoint.Location = Location(latlng[0], latlng[1], None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None else: # strava only returns 0 as invalid coords, so no need to check for null (update: ??) hasLocation = True if hasAltitude: waypoint.Location.Altitude = float(ridedata["altitude"][idx]) if idx == 0: waypoint.Type = WaypointType.Start elif idx == waypointCt - 2: waypoint.Type = WaypointType.End elif hasMovingData and not moving and ridedata["moving"][ idx] is True: waypoint.Type = WaypointType.Resume moving = True elif hasMovingData and ridedata["moving"][idx] is False: waypoint.Type = WaypointType.Pause moving = False if hasHR: waypoint.HR = ridedata["heartrate"][idx] if hasCadence: waypoint.Cadence = ridedata["cadence"][idx] if hasTemp: waypoint.Temp = ridedata["temp"][idx] if hasPower: waypoint.Power = ridedata["watts"][idx] lap.Waypoints.append(waypoint) if not hasLocation: self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), "faulty") raise APIExcludeActivity("No waypoints with location", activityId=activityID, userException=UserException( UserExceptionType.Corrupt)) self._logAPICall("download", (svcRecord.ExternalID, str(activity.StartTime)), None) return activity
def _populateActivityFromTrackData(self, activity, recordText, minimumWaypoints=False): lap = Lap() activity.Laps = [lap] ### 1ST RECORD ### # userID; # timestamp - create date?; # type? W=1st # User name; # activity name; # activity type; # another timestamp - start time of event?; # duration.00; # distance (km); # kcal; #; # max alt; # min alt; # max HR; # avg HR; ### TRACK RECORDS ### # timestamp; # type (2=start, 3=end, 0=pause, 1=resume); # latitude; # longitude; #; #; # alt; # hr; wptsWithLocation = False wptsWithNonZeroAltitude = False rows = recordText.split("\n") for row in rows: if row == "OK" or len(row) == 0: continue split = row.split(";") if split[2] == "W": # init record lap.Stats.MovingTime = ActivityStatistic(ActivityStatisticUnit.Time, value=timedelta(seconds=float(split[7])) if split[7] != "" else None) lap.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Kilometers, value=float(split[8]) if split[8] != "" else None) lap.Stats.HR = ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=float(split[14]) if split[14] != "" else None, max=float(split[13]) if split[13] != "" else None) lap.Stats.Elevation = ActivityStatistic(ActivityStatisticUnit.Meters, min=float(split[12]) if split[12] != "" else None, max=float(split[11]) if split[11] != "" else None) lap.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilocalories, value=float(split[12]) if split[12] != "" else None) activity.Stats.update(lap.Stats) lap.Stats = activity.Stats activity.Name = split[4] else: wp = Waypoint() if split[1] == "2": wp.Type = WaypointType.Start elif split[1] == "3": wp.Type = WaypointType.End elif split[1] == "0": wp.Type = WaypointType.Pause elif split[1] == "1": wp.Type = WaypointType.Resume else: wp.Type == WaypointType.Regular if split[0] == "": continue # no timestamp, for whatever reason wp.Timestamp = pytz.utc.localize(datetime.strptime(split[0], "%Y-%m-%d %H:%M:%S UTC")) # it's like this as opposed to %z so I know when they change things (it'll break) if split[2] != "": wp.Location = Location(float(split[2]), float(split[3]), None) if wp.Location.Longitude > 180 or wp.Location.Latitude > 90 or wp.Location.Longitude < -180 or wp.Location.Latitude < -90: raise APIExcludeActivity("Out of range lat/lng") if wp.Location.Latitude is not None and wp.Location.Latitude is not None: wptsWithLocation = True if split[6] != "": wp.Location.Altitude = float(split[6]) # why this is missing: who knows? if wp.Location.Altitude != 0: wptsWithNonZeroAltitude = True if split[7] != "": wp.HR = float(split[7]) lap.Waypoints.append(wp) if wptsWithLocation and minimumWaypoints: break lap.Waypoints = sorted(activity.Waypoints, key=lambda v: v.Timestamp) if wptsWithLocation: activity.EnsureTZ(recalculate=True) if not wptsWithNonZeroAltitude: # do this here so, should the activity run near sea level, altitude data won't be spotty for x in lap.Waypoints: # clear waypoints of altitude data if all of them were logged at 0m (invalid) if x.Location is not None: x.Location.Altitude = None else: lap.Waypoints = [] # practically speaking
def _populateActivityFromTrackData(self, activity, recordText, minimumWaypoints=False): lap = Lap() activity.Laps = [lap] ### 1ST RECORD ### # userID; # timestamp - create date?; # type? W=1st # User name; # activity name; # activity type; # another timestamp - start time of event?; # duration.00; # distance (km); # kcal; #; # max alt; # min alt; # max HR; # avg HR; ### TRACK RECORDS ### # timestamp; # type (2=start, 3=end, 0=pause, 1=resume); # latitude; # longitude; #; #; # alt; # hr; wptsWithLocation = False wptsWithNonZeroAltitude = False rows = recordText.split("\n") for row in rows: if row == "OK" or len(row) == 0: continue split = row.split(";") if split[2] == "W": # init record lap.Stats.TimerTime = ActivityStatistic( ActivityStatisticUnit.Time, value=timedelta( seconds=float(split[7])) if split[7] != "" else None) lap.Stats.Distance = ActivityStatistic( ActivityStatisticUnit.Kilometers, value=float(split[8]) if split[8] != "" else None) lap.Stats.HR = ActivityStatistic( ActivityStatisticUnit.BeatsPerMinute, avg=float(split[14]) if split[14] != "" else None, max=float(split[13]) if split[13] != "" else None) lap.Stats.Elevation = ActivityStatistic( ActivityStatisticUnit.Meters, min=float(split[12]) if split[12] != "" else None, max=float(split[11]) if split[11] != "" else None) lap.Stats.Energy = ActivityStatistic( ActivityStatisticUnit.Kilocalories, value=float(split[12]) if split[12] != "" else None) activity.Stats.update(lap.Stats) lap.Stats = activity.Stats activity.Name = split[4] else: wp = Waypoint() if split[1] == "2": wp.Type = WaypointType.Start elif split[1] == "3": wp.Type = WaypointType.End elif split[1] == "0": wp.Type = WaypointType.Pause elif split[1] == "1": wp.Type = WaypointType.Resume else: wp.Type == WaypointType.Regular if split[0] == "": continue # no timestamp, for whatever reason wp.Timestamp = pytz.utc.localize( datetime.strptime(split[0], "%Y-%m-%d %H:%M:%S UTC") ) # it's like this as opposed to %z so I know when they change things (it'll break) if split[2] != "": wp.Location = Location(float(split[2]), float(split[3]), None) if wp.Location.Longitude > 180 or wp.Location.Latitude > 90 or wp.Location.Longitude < -180 or wp.Location.Latitude < -90: raise APIExcludeActivity("Out of range lat/lng") if wp.Location.Latitude is not None and wp.Location.Latitude is not None: wptsWithLocation = True if split[6] != "": wp.Location.Altitude = float( split[6]) # why this is missing: who knows? if wp.Location.Altitude != 0: wptsWithNonZeroAltitude = True if split[7] != "": wp.HR = float(split[7]) lap.Waypoints.append(wp) if wptsWithLocation and minimumWaypoints: break lap.Waypoints = sorted(activity.Waypoints, key=lambda v: v.Timestamp) if wptsWithLocation: if not wptsWithNonZeroAltitude: # do this here so, should the activity run near sea level, altitude data won't be spotty for x in lap.Waypoints: # clear waypoints of altitude data if all of them were logged at 0m (invalid) if x.Location is not None: x.Location.Altitude = None else: lap.Waypoints = [] # practically speaking
def DownloadActivity(self, svcRecord, activity): if activity.ServiceData["Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats)] return activity activityID = activity.ServiceData["ActivityID"] streamdata = self._requestWithAuth(lambda session: session.get("https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng,distance,velocity_smooth"), svcRecord) if streamdata.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata["message"] == "Record Not Found": raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) # Strava doesn't support laps, but we need somewhere to put the waypoints. activity.Laps = [lap] lap.Waypoints = [] hasHR = "heartrate" in ridedata and len(ridedata["heartrate"]) > 0 hasCadence = "cadence" in ridedata and len(ridedata["cadence"]) > 0 hasTemp = "temp" in ridedata and len(ridedata["temp"]) > 0 hasPower = ("watts" in ridedata and len(ridedata["watts"]) > 0) hasAltitude = "altitude" in ridedata and len(ridedata["altitude"]) > 0 hasDistance = "distance" in ridedata and len(ridedata["distance"]) > 0 hasVelocity = "velocity_smooth" in ridedata and len(ridedata["velocity_smooth"]) > 0 if "error" in ridedata: raise APIException("Strava error " + ridedata["error"]) inPause = False waypointCt = len(ridedata["time"]) for idx in range(0, waypointCt - 1): waypoint = Waypoint(activity.StartTime + timedelta(0, ridedata["time"][idx])) if "latlng" in ridedata: latlng = ridedata["latlng"][idx] waypoint.Location = Location(latlng[0], latlng[1], None) if waypoint.Location.Longitude == 0 and waypoint.Location.Latitude == 0: waypoint.Location.Longitude = None waypoint.Location.Latitude = None if hasAltitude: if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = float(ridedata["altitude"][idx]) # When pausing, Strava sends this format: # idx = 100 ; time = 1000; moving = true # idx = 101 ; time = 1001; moving = true => convert to Pause # idx = 102 ; time = 2001; moving = false => convert to Resume: (2001-1001) seconds pause # idx = 103 ; time = 2002; moving = true if idx == 0: waypoint.Type = WaypointType.Start elif idx == waypointCt - 2: waypoint.Type = WaypointType.End elif idx < waypointCt - 2 and ridedata["moving"][idx+1] and inPause: waypoint.Type = WaypointType.Resume inPause = False elif idx < waypointCt - 2 and not ridedata["moving"][idx+1] and not inPause: waypoint.Type = WaypointType.Pause inPause = True if hasHR: waypoint.HR = ridedata["heartrate"][idx] if hasCadence: waypoint.Cadence = ridedata["cadence"][idx] if hasTemp: waypoint.Temp = ridedata["temp"][idx] if hasPower: waypoint.Power = ridedata["watts"][idx] if hasVelocity: waypoint.Speed = ridedata["velocity_smooth"][idx] if hasDistance: waypoint.Distance = ridedata["distance"][idx] lap.Waypoints.append(waypoint) return activity