Skip to content

Commit

Permalink
Merge pull request #359 from toft-software/master
Browse files Browse the repository at this point in the history
Setio integrates with tapiriik
  • Loading branch information
cpfair authored Aug 11, 2017
2 parents 0e97b8e + ac9dfef commit d2ed99b
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 2 deletions.
3 changes: 3 additions & 0 deletions tapiriik/local_settings.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ RUNKEEPER_CLIENT_SECRET="####"

RWGPS_APIKEY = "####"

SETIO_CLIENT_ID = "####"
SETIO_CLIENT_SECRET = "####"

# See http://api.smashrun.com for info.
# For now, you need to email [email protected] for access
SMASHRUN_CLIENT_ID = "####"
Expand Down
1 change: 1 addition & 0 deletions tapiriik/services/Setio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .setio import *
253 changes: 253 additions & 0 deletions tapiriik/services/Setio/setio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#
# Created by Christian Toft Andersen 2017 for SETIO @
#
from tapiriik.settings import WEB_ROOT, SETIO_CLIENT_SECRET, SETIO_CLIENT_ID
from tapiriik.services.service_base import ServiceAuthenticationType, ServiceBase
from tapiriik.services.interchange import UploadedActivity, ActivityType, ActivityStatistic, ActivityStatisticUnit, \
Waypoint, WaypointType, Location, Lap

from django.core.urlresolvers import reverse
from datetime import datetime
from urllib.parse import urlencode
import requests
import logging
import dateutil.parser
import json

logger = logging.getLogger(__name__)


class SetioService(ServiceBase):
ID = "setio"
DisplayName = "Setio"
DisplayAbbreviation = "SET"
AuthenticationType = ServiceAuthenticationType.OAuth
AuthenticationNoFrame = True # They don't prevent the iframe, it just looks really ugly.
PartialSyncRequiresTrigger = False
LastUpload = None
SetioDomain = "https://us-central1-project-2489250248063150762.cloudfunctions.net/"

SupportsHR = SupportsCadence = SupportsTemp = SupportsPower = True

SupportsActivityDeletion = True

# For mapping common->Setio; no ambiguity in Setio activity type
_activityTypeMappings = {
ActivityType.Cycling: "Ride",
ActivityType.MountainBiking: "Ride",
ActivityType.Hiking: "Hike",
ActivityType.Running: "Run",
ActivityType.Walking: "Walk",
ActivityType.Snowboarding: "Snowboard",
ActivityType.Skating: "IceSkate",
ActivityType.CrossCountrySkiing: "NordicSki",
ActivityType.DownhillSkiing: "AlpineSki",
ActivityType.Swimming: "Swim",
ActivityType.Gym: "Workout",
ActivityType.Rowing: "Rowing",
ActivityType.Elliptical: "Elliptical",
ActivityType.RollerSkiing: "RollerSki",
ActivityType.StrengthTraining: "WeightTraining",
}

# For mapping Setio->common
_reverseActivityTypeMappings = {
"Ride": ActivityType.Cycling,
"VirtualRide": ActivityType.Cycling,
"EBikeRide": ActivityType.Cycling,
"MountainBiking": ActivityType.MountainBiking,
"Run": ActivityType.Running,
"Hike": ActivityType.Hiking,
"Walk": ActivityType.Walking,
"AlpineSki": ActivityType.DownhillSkiing,
"CrossCountrySkiing": ActivityType.CrossCountrySkiing,
"NordicSki": ActivityType.CrossCountrySkiing,
"BackcountrySki": ActivityType.DownhillSkiing,
"Snowboard": ActivityType.Snowboarding,
"Swim": ActivityType.Swimming,
"IceSkate": ActivityType.Skating,
"Workout": ActivityType.Gym,
"Rowing": ActivityType.Rowing,
"Kayaking": ActivityType.Rowing,
"Canoeing": ActivityType.Rowing,
"StandUpPaddling": ActivityType.Rowing,
"Elliptical": ActivityType.Elliptical,
"RollerSki": ActivityType.RollerSkiing,
"WeightTraining": ActivityType.StrengthTraining,
}

SupportedActivities = list(_activityTypeMappings.keys())

def WebInit(self):
params = {'scope': 'write,view_private',
'client_id': SETIO_CLIENT_ID,
'response_type': 'code',
'redirect_uri': WEB_ROOT + reverse("oauth_return", kwargs={"service": "setio"})}
self.UserAuthorizationURL = \
"https://setio.run/oauth/authorize?" + urlencode(params)

def _apiHeaders(self, serviceRecord):
return {"Authorization": "access_token " + serviceRecord.Authorization["OAuthToken"]}

def RetrieveAuthorizationToken(self, req, level):
code = req.GET.get("code")
authorizationData = {"OAuthToken": code}
return (code, authorizationData)

def RevokeAuthorization(self, serviceRecord):
# you can't revoke the tokens setio distributes :\
pass

def DownloadActivityList(self, svcRecord, exhaustive=False):
activities = []
exclusions = []

url = self.SetioDomain + "getRunsByUserId"
extID = svcRecord.ExternalID

payload = {"userId": extID}
headers = {
'content-type': "application/json",
'cache-control': "no-cache",
}
response = requests.post(url, data=json.dumps(payload), headers=headers)
try:
reqdata = response.json()
except ValueError:
raise APIException("Failed parsing Setio list response %s - %s" % (resp.status_code, resp.text))

for ride in reqdata:
activity = UploadedActivity()
activity.StartTime = datetime.strptime(
datetime.utcfromtimestamp(ride["startTimeStamp"]).strftime('%Y-%m-%d %H:%M:%S'), "%Y-%m-%d %H:%M:%S")
if "stopTimeStamp" in ride:
activity.EndTime = datetime.strptime(
datetime.utcfromtimestamp(ride["stopTimeStamp"]).strftime('%Y-%m-%d %H:%M:%S'), "%Y-%m-%d %H:%M:%S")
activity.ServiceData = {"ActivityID": ride["runId"], "Manual": "False"}

activity.Name = ride["programName"]

logger.debug("\tActivity s/t %s: %s" % (activity.StartTime, activity.Name))
activity.Type = ActivityType.Running
if "totalDistance" in ride:
activity.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=ride["totalDistance"])

if "averageCadence" in ride:
activity.Stats.Cadence.update(
ActivityStatistic(ActivityStatisticUnit.RevolutionsPerMinute, avg=ride["averageCadence"]))

if "averageSpeed" in ride:
activity.Stats.Speed = ActivityStatistic(ActivityStatisticUnit.MetersPerSecond,
avg=ride["averageSpeed"])

# get comment
url = self.SetioDomain + "getRunComment"
payload = { "userId": extID, "runId": activity.ServiceData["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:
commentdata = streamdata.json()
except:
raise APIException("Stream data returned is not JSON")

if "comment" in commentdata:
activity.Notes = commentdata["comment"]
else:
activity.Notes = None
else:
activity.Notes = None

activity.GPS = True

activity.Private = False
activity.Stationary = False # True = no sensor data

activity.CalculateUID()
activities.append(activity)

return activities, exclusions

def DownloadActivity(self, svcRecord, activity):

activityID = activity.ServiceData["ActivityID"]
extID = svcRecord.ExternalID
url = self.SetioDomain + "getRunData"
payload = {"userId": extID, "runId": 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) # Setio 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 "cadence" in stream:
waypoint.Cadence = stream["cadence"]
waypoint.Cadence = waypoint.Cadence / 2

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 UploadActivity(self, serviceRecord, activity):
pass

def DeleteCachedData(self, serviceRecord):
pass

def DeleteActivity(self, serviceRecord, uploadId):
pass
6 changes: 4 additions & 2 deletions tapiriik/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def List():
VeloHero,
TrainerRoad,
Smashrun,
BeginnerTriathlete
BeginnerTriathlete,
Setio
] + PRIVATE_SERVICES

def PreferredDownloadPriorityList():
Expand All @@ -64,7 +65,8 @@ def PreferredDownloadPriorityList():
BeginnerTriathlete, # No temperature
Motivato,
NikePlus,
Pulsstory
Pulsstory,
Setio
] + PRIVATE_SERVICES

def WebInit():
Expand Down
Binary file added tapiriik/web/static/img/services/Setio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tapiriik/web/static/img/services/Setio_l.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d2ed99b

Please sign in to comment.