def DownloadActivity(self, serviceRecord, activity): resp = self._oauthSession(serviceRecord).get("https://api.endomondo.com/api/1/workouts/%d" % activity.ServiceData["WorkoutID"], params={"fields": "points"}) try: resp = resp.json() except ValueError: self._rateLimitBailout(resp) res_txt = resp.text raise APIException("Parse failure in Endomondo activity download: %s" % resp.status_code) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False old_location = None in_pause = False for pt in resp["points"]: wp = Waypoint() if "time" not in pt: # Manually-entered activities with a course attached to them have date-less waypoints # It'd be nice to transfer those courses, but it's a concept few other sites support AFAIK # So, ignore the points entirely continue wp.Timestamp = self._parseDate(pt["time"]) if ("lat" in pt and "lng" in pt) or "alt" in pt: wp.Location = Location() if "lat" in pt and "lng" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lng"] activity.GPS = True if "alt" in pt: wp.Location.Altitude = pt["alt"] if wp.Location == old_location: # We have seen the point with the same coordinates # before. This causes other services (e.g Strava) to # interpret this as if we were standing for a while, # which causes us having wrong activity time when # importing. We mark the point as paused in hopes this # fixes the issue. in_pause = True wp.Type = WaypointType.Pause elif in_pause: in_pause = False wp.Type = WaypointType.Resume old_location = wp.Location if "hr" in pt: wp.HR = pt["hr"] if "cad" in pt: wp.Cadence = pt["cad"] if "pow" in pt: wp.Power = pt["pow"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): resp = self._oauthSession(serviceRecord).get("https://api.endomondo.com/api/1/workouts/%d" % activity.ServiceData["WorkoutID"], params={"fields": "points"}) try: resp = resp.json() except ValueError: res_txt = resp.text raise APIException("Parse failure in Endomondo activity download: %s" % resp.status_code) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False for pt in resp["points"]: wp = Waypoint() wp.Timestamp = self._parseDate(pt["time"]) if ("lat" in pt and "lng" in pt) or "alt" in pt: wp.Location = Location() if "lat" in pt and "lng" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lng"] activity.GPS = True if "alt" in pt: wp.Location.Altitude = pt["alt"] if "hr" in pt: wp.HR = pt["hr"] if "cad" in pt: wp.Cadence = pt["cad"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): resp = self._oauthSession(serviceRecord).get("https://api.endomondo.com/api/1/workouts/%d" % activity.ServiceData["WorkoutID"], params={"fields": "points"}) try: resp = resp.json() except ValueError: self._rateLimitBailout(resp) res_txt = resp.text raise APIException("Parse failure in Endomondo activity download: %s" % resp.status_code) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False prevLon = 0.0 prevLat = 0.0 for pt in resp["points"]: wp = Waypoint() if "time" not in pt: # Manually-entered activities with a course attached to them have date-less waypoints # It'd be nice to transfer those courses, but it's a concept few other sites support AFAIK # So, ignore the points entirely continue wp.Timestamp = self._parseDate(pt["time"]) if ("lat" in pt and "lng" in pt) or "alt" in pt: wp.Location = Location() if "lat" in pt and "lng" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lng"] if (wp.Location.Latitude == prevLat and wp.Location.Longitude == prevLon): # we have seen the point with the same coordinates # before. This causes other services (e.g Strava) to # interpret this as if we were standing for a while, # which causes us having wrong activity time when # importing. We discard this entry to keep only unique # ones to avoid this. This is still a hack :( However, # I don't know if this will handle the situation when # we are actually standing for some time in one place # well... continue; prevLat = wp.Location.Latitude; prevLon = wp.Location.Longitude; activity.GPS = True if "alt" in pt: wp.Location.Altitude = pt["alt"] if "hr" in pt: wp.HR = pt["hr"] if "cad" in pt: wp.Cadence = pt["cad"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def stream_waypoint(offset, speed=None, distance=None, heartrate=None, calories=None, steps=None, watts=None, gps=None, **kwargs): wp = Waypoint() wp.Timestamp = activity.StartTime + timedelta(seconds=offset) wp.Speed = float(speed) if speed else None wp.Distance = float(distance) / 1000 if distance else None wp.HR = float(heartrate) if heartrate else None wp.Calories = float(calories) if calories else None wp.Power = float(watts) if watts else None if gps: wp.Location = Location(lat=float(gps["latitude"]), lon=float(gps["longitude"]), alt=float(gps["elevation"])) lap.Waypoints.append(wp)
def DownloadActivity(self, serviceRecord, activity): resp = self._oauthSession(serviceRecord).get( "https://api.endomondo.com/api/1/workouts/%d" % activity.ServiceData["WorkoutID"], params={"fields": "points"}) try: resp = resp.json() except ValueError: self._rateLimitBailout(resp) res_txt = resp.text raise APIException( "Parse failure in Endomondo activity download: %s" % resp.status_code) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False for pt in resp["points"]: wp = Waypoint() if "time" not in pt: # Manually-entered activities with a course attached to them have date-less waypoints # It'd be nice to transfer those courses, but it's a concept few other sites support AFAIK # So, ignore the points entirely continue wp.Timestamp = self._parseDate(pt["time"]) if ("lat" in pt and "lng" in pt) or "alt" in pt: wp.Location = Location() if "lat" in pt and "lng" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lng"] activity.GPS = True if "alt" in pt: wp.Location.Altitude = pt["alt"] if "hr" in pt: wp.HR = pt["hr"] if "cad" in pt: wp.Cadence = pt["cad"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): workoutID = activity.ServiceData["WorkoutID"] logger.debug("DownloadActivity for %s" % workoutID) session = self._get_session(record=serviceRecord) resp = session.get(self._urlRoot + "/api/workout/%d" % workoutID) try: res = resp.json() except ValueError: raise APIException( "Parse failure in Motivato activity (%d) download: %s" % (workoutID, res.text)) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False if "track" in res and "points" in res["track"]: for pt in res["track"]["points"]: wp = Waypoint() if "moment" not in pt: continue wp.Timestamp = self._parseDateTime(pt["moment"]) if ("lat" in pt and "lon" in pt) or "ele" in pt: wp.Location = Location() if "lat" in pt and "lon" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lon"] activity.GPS = True if "ele" in pt: wp.Location.Altitude = float(pt["ele"]) if "bpm" in pt: wp.HR = pt["bpm"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): resp = self._oauthSession(serviceRecord).get( "https://api.endomondo.com/api/1/workouts/%d" % activity.ServiceData["WorkoutID"], params={"fields": "points"}) try: resp = resp.json() except ValueError: res_txt = resp.text raise APIException( "Parse failure in Endomondo activity download: %s" % resp.status_code) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False for pt in resp["points"]: wp = Waypoint() wp.Timestamp = self._parseDate(pt["time"]) if ("lat" in pt and "lng" in pt) or "alt" in pt: wp.Location = Location() if "lat" in pt and "lng" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lng"] activity.GPS = True if "alt" in pt: wp.Location.Altitude = pt["alt"] if "hr" in pt: wp.HR = pt["hr"] if "cad" in pt: wp.Cadence = pt["cad"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): workoutID = activity.ServiceData["WorkoutID"] logger.debug("DownloadActivity for %s" % workoutID); session = self._get_session(record=serviceRecord) resp = session.get(self._urlRoot + "/api/workout/%d" % workoutID) try: res = resp.json() except ValueError: raise APIException("Parse failure in Motivato activity (%d) download: %s" % (workoutID, res.text)) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False if "track" in res and "points" in res["track"]: for pt in res["track"]["points"]: wp = Waypoint() if "moment" not in pt: continue wp.Timestamp = self._parseDateTime(pt["moment"]) if ("lat" in pt and "lon" in pt) or "ele" in pt: wp.Location = Location() if "lat" in pt and "lon" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lon"] activity.GPS = True if "ele" in pt: wp.Location.Altitude = float(pt["ele"]) if "bpm" in pt: wp.HR = pt["bpm"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): resp = self._oauthSession(serviceRecord).get("https://api.endomondo.com/api/1/workouts/%d" % activity.ServiceData["WorkoutID"], params={"fields": "points"}) try: resp = resp.json() except ValueError: self._rateLimitBailout(resp) res_txt = resp.text raise APIException("Parse failure in Endomondo activity download: %s" % resp.status_code) lap = Lap(stats=activity.Stats, startTime=activity.StartTime, endTime=activity.EndTime) activity.Laps = [lap] activity.GPS = False for pt in resp["points"]: wp = Waypoint() if "time" not in pt: # Manually-entered activities with a course attached to them have date-less waypoints # It'd be nice to transfer those courses, but it's a concept few other sites support AFAIK # So, ignore the points entirely continue wp.Timestamp = self._parseDate(pt["time"]) if ("lat" in pt and "lng" in pt) or "alt" in pt: wp.Location = Location() if "lat" in pt and "lng" in pt: wp.Location.Latitude = pt["lat"] wp.Location.Longitude = pt["lng"] activity.GPS = True if "alt" in pt: wp.Location.Altitude = pt["alt"] if "hr" in pt: wp.HR = pt["hr"] if "cad" in pt: wp.Cadence = pt["cad"] lap.Waypoints.append(wp) activity.Stationary = len(lap.Waypoints) == 0 return activity
def create_random_activity(svc=None, actType=ActivityType.Other, tz=False, record=None, withPauses=True, withLaps=True): ''' creates completely random activity with valid waypoints and data ''' act = TestTools.create_blank_activity(svc, actType, record=record) if tz is True: tz = pytz.timezone("America/Atikokan") act.TZ = tz elif tz is not False: act.TZ = tz if act.CountTotalWaypoints() > 0: raise ValueError("Waypoint list already populated") # this is entirely random in case the testing account already has events in it (API doesn't support delete, etc) act.StartTime = datetime(2011, 12, 13, 14, 15, 16) if tz is not False: if hasattr(tz, "localize"): act.StartTime = tz.localize(act.StartTime) else: act.StartTime = act.StartTime.replace(tzinfo=tz) act.EndTime = act.StartTime + timedelta( 0, random.randint(60 * 5, 60 * 60) ) # don't really need to upload 1000s of pts to test this... act.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=random.random() * 10000) act.Name = str(random.random()) paused = False waypointTime = act.StartTime backToBackPauses = False act.Laps = [] lap = Lap(startTime=act.StartTime) while waypointTime < act.EndTime: wp = Waypoint() if waypointTime == act.StartTime: wp.Type = WaypointType.Start wp.Timestamp = waypointTime wp.Location = Location( random.random() * 180 - 90, random.random() * 180 - 90, random.random() * 1000) # this is gonna be one intense activity if not (wp.HR == wp.Cadence == wp.Calories == wp.Power == wp.Temp == None): raise ValueError("Waypoint did not initialize cleanly") if svc.SupportsHR: wp.HR = float(random.randint(90, 180)) if svc.SupportsPower: wp.Power = float(random.randint(0, 1000)) if svc.SupportsCalories: wp.Calories = float(random.randint(0, 500)) if svc.SupportsCadence: wp.Cadence = float(random.randint(0, 100)) if svc.SupportsTemp: wp.Temp = float(random.randint(0, 100)) if withPauses and (random.randint(40, 50) == 42 or backToBackPauses ) and not paused: # pause quite often wp.Type = WaypointType.Pause paused = True elif paused: paused = False wp.Type = WaypointType.Resume backToBackPauses = not backToBackPauses waypointTime += timedelta(0, int(random.random() + 9.5)) # 10ish seconds lap.Waypoints.append(wp) if waypointTime > act.EndTime: wp.Timestamp = act.EndTime wp.Type = WaypointType.End elif withLaps and wp.Timestamp < act.EndTime and random.randint( 40, 60) == 42: # occasionally start new laps lap.EndTime = wp.Timestamp act.Laps.append(lap) lap = Lap(startTime=waypointTime) # Final lap lap.EndTime = act.EndTime act.Laps.append(lap) if act.CountTotalWaypoints() == 0: raise ValueError("No waypoints populated") act.CalculateUID() act.EnsureTZ() return act
def _populateActivityFromTrackData(self, activity, recordText, minimumWaypoints=False): activity.Waypoints = [] ### 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 activity.Distance = float( split[8]) * 1000 if split[8] != "" else None 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]) activity.Waypoints.append(wp) if wptsWithLocation and minimumWaypoints: break activity.Waypoints = sorted(activity.Waypoints, key=lambda v: v.Timestamp) if wptsWithLocation: activity.EnsureTZ() if not wptsWithNonZeroAltitude: # do this here so, should the activity run near sea level, altitude data won't be spotty for x in activity.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: activity.Waypoints = [] # practically speaking
def create_random_activity(svc=None, actType=ActivityType.Other, tz=False, record=None): ''' creates completely random activity with valid waypoints and data ''' act = TestTools.create_blank_activity(svc, actType, record=record) if tz is True: tz = pytz.timezone(pytz.all_timezones[random.randint( 0, len(pytz.all_timezones) - 1)]) act.TZ = tz elif tz is not False: act.TZ = tz if len(act.Waypoints) > 0: raise ValueError("Waypoint list already populated") # this is entirely random in case the testing account already has events in it (API doesn't support delete, etc) act.StartTime = datetime(random.randint(2000, 2020), random.randint(1, 12), random.randint(1, 28), random.randint(0, 23), random.randint(0, 59), random.randint(0, 59)) if tz is not False: if hasattr(tz, "localize"): act.StartTime = tz.localize(act.StartTime) else: act.StartTime = act.StartTime.replace(tzinfo=tz) act.EndTime = act.StartTime + timedelta( 0, random.randint(60 * 5, 60 * 60) ) # don't really need to upload 1000s of pts to test this... act.Distance = random.random() * 10000 act.Name = str(random.random()) paused = False waypointTime = act.StartTime backToBackPauses = False while waypointTime < act.EndTime: wp = Waypoint() if waypointTime == act.StartTime: wp.Type = WaypointType.Start wp.Timestamp = waypointTime wp.Location = Location( random.random() * 180 - 90, random.random() * 180 - 90, random.random() * 1000) # this is gonna be one intense activity if not (wp.HR == wp.Cadence == wp.Calories == wp.Power == wp.Temp == None): raise ValueError("Waypoint did not initialize cleanly") if svc.SupportsHR: wp.HR = float(random.randint(90, 180)) if svc.SupportsPower: wp.Power = float(random.randint(0, 1000)) if svc.SupportsCalories: wp.Calories = float(random.randint(0, 500)) if svc.SupportsCadence: wp.Cadence = float(random.randint(0, 100)) if svc.SupportsTemp: wp.Temp = float(random.randint(0, 100)) if (random.randint(40, 50) == 42 or backToBackPauses) and not paused: # pause quite often wp.Type = WaypointType.Pause paused = True elif paused: paused = False wp.Type = WaypointType.Resume backToBackPauses = not backToBackPauses waypointTime += timedelta(0, int(random.random() + 9.5)) # 10ish seconds if waypointTime > act.EndTime: wp.Timestamp = act.EndTime wp.Type = WaypointType.End act.Waypoints.append(wp) if len(act.Waypoints) == 0: raise ValueError("No waypoints populated") return act
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 create_random_activity(svc=None, actType=ActivityType.Other, tz=False, record=None): ''' creates completely random activity with valid waypoints and data ''' act = TestTools.create_blank_activity(svc, actType, record=record) if tz is True: tz = pytz.timezone(pytz.all_timezones[random.randint(0, len(pytz.all_timezones) - 1)]) act.TZ = tz elif tz is not False: act.TZ = tz if len(act.Waypoints) > 0: raise ValueError("Waypoint list already populated") # this is entirely random in case the testing account already has events in it (API doesn't support delete, etc) act.StartTime = datetime(random.randint(2000, 2020), random.randint(1, 12), random.randint(1, 28), random.randint(0, 23), random.randint(0, 59), random.randint(0, 59)) if tz is not False: if hasattr(tz, "localize"): act.StartTime = tz.localize(act.StartTime) else: act.StartTime = act.StartTime.replace(tzinfo=tz) act.EndTime = act.StartTime + timedelta(0, random.randint(60 * 5, 60 * 60)) # don't really need to upload 1000s of pts to test this... act.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=random.random() * 10000) act.Name = str(random.random()) paused = False waypointTime = act.StartTime backToBackPauses = False while waypointTime < act.EndTime: wp = Waypoint() if waypointTime == act.StartTime: wp.Type = WaypointType.Start wp.Timestamp = waypointTime wp.Location = Location(random.random() * 180 - 90, random.random() * 180 - 90, random.random() * 1000) # this is gonna be one intense activity if not (wp.HR == wp.Cadence == wp.Calories == wp.Power == wp.Temp == None): raise ValueError("Waypoint did not initialize cleanly") if svc.SupportsHR: wp.HR = float(random.randint(90, 180)) if svc.SupportsPower: wp.Power = float(random.randint(0, 1000)) if svc.SupportsCalories: wp.Calories = float(random.randint(0, 500)) if svc.SupportsCadence: wp.Cadence = float(random.randint(0, 100)) if svc.SupportsTemp: wp.Temp = float(random.randint(0, 100)) if (random.randint(40, 50) == 42 or backToBackPauses) and not paused: # pause quite often wp.Type = WaypointType.Pause paused = True elif paused: paused = False wp.Type = WaypointType.Resume backToBackPauses = not backToBackPauses waypointTime += timedelta(0, int(random.random() + 9.5)) # 10ish seconds if waypointTime > act.EndTime: wp.Timestamp = act.EndTime wp.Type = WaypointType.End act.Waypoints.append(wp) if len(act.Waypoints) == 0: raise ValueError("No waypoints populated") return act
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 _populateActivityFromTrackRecord(self, activity, recordText, minimumWaypoints=False): activity.Waypoints = [] ### 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 activity.Distance = float(split[8]) * 1000 if split[8] != "" else None 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.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]) activity.Waypoints.append(wp) if wptsWithLocation and minimumWaypoints: break if wptsWithLocation: activity.EnsureTZ() if not wptsWithNonZeroAltitude: # do this here so, should the activity run near sea level, altitude data won't be spotty for x in activity.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: activity.Waypoints = [] # practically speaking
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): 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 create_random_activity(svc=None, actType=ActivityType.Other, tz=False, record=None, withPauses=True, withLaps=True): ''' creates completely random activity with valid waypoints and data ''' act = TestTools.create_blank_activity(svc, actType, record=record) if tz is True: tz = pytz.timezone("America/Atikokan") act.TZ = tz elif tz is not False: act.TZ = tz if act.CountTotalWaypoints() > 0: raise ValueError("Waypoint list already populated") # this is entirely random in case the testing account already has events in it (API doesn't support delete, etc) act.StartTime = datetime(2011, 12, 13, 14, 15, 16) if tz is not False: if hasattr(tz, "localize"): act.StartTime = tz.localize(act.StartTime) else: act.StartTime = act.StartTime.replace(tzinfo=tz) act.EndTime = act.StartTime + timedelta(0, random.randint(60 * 5, 60 * 60)) # don't really need to upload 1000s of pts to test this... act.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=random.random() * 10000) act.Name = str(random.random()) paused = False waypointTime = act.StartTime backToBackPauses = False act.Laps = [] lap = Lap(startTime=act.StartTime) while waypointTime < act.EndTime: wp = Waypoint() if waypointTime == act.StartTime: wp.Type = WaypointType.Start wp.Timestamp = waypointTime wp.Location = Location(random.random() * 180 - 90, random.random() * 180 - 90, random.random() * 1000) # this is gonna be one intense activity if not (wp.HR == wp.Cadence == wp.Calories == wp.Power == wp.Temp == None): raise ValueError("Waypoint did not initialize cleanly") if svc.SupportsHR: wp.HR = float(random.randint(90, 180)) if svc.SupportsPower: wp.Power = float(random.randint(0, 1000)) if svc.SupportsCalories: wp.Calories = float(random.randint(0, 500)) if svc.SupportsCadence: wp.Cadence = float(random.randint(0, 100)) if svc.SupportsTemp: wp.Temp = float(random.randint(0, 100)) if withPauses and (random.randint(40, 50) == 42 or backToBackPauses) and not paused: # pause quite often wp.Type = WaypointType.Pause paused = True elif paused: paused = False wp.Type = WaypointType.Resume backToBackPauses = not backToBackPauses waypointTime += timedelta(0, int(random.random() + 9.5)) # 10ish seconds lap.Waypoints.append(wp) if waypointTime > act.EndTime: wp.Timestamp = act.EndTime wp.Type = WaypointType.End elif withLaps and wp.Timestamp < act.EndTime and random.randint(40, 60) == 42: # occasionally start new laps lap.EndTime = wp.Timestamp act.Laps.append(lap) lap = Lap(startTime=waypointTime) # Final lap lap.EndTime = act.EndTime act.Laps.append(lap) if act.CountTotalWaypoints() == 0: raise ValueError("No waypoints populated") act.CalculateUID() act.EnsureTZ() return act