Toegang krijgen tot de OMNI API

OlivierVK
Pro
Pro
Belgium
Berichten: 37
Lid geworden op: 24 apr 2021, 09:58
Locatie: Ninove
Merk SP: Stromer
Km-stand: 1510

Ik heb het werkend gekregen :d3:

Via sniffing gezien dat - op basis van de app zoals geinstalleerd op mijn iphone - de API calls verschillend zijn.
Ik heb een python scriptje gemaakt dat nu correct werkt.

Ik zal de details later dit weekend posten voor de geïnteresseerden.

Groetjes
Stromer ST5 - 2020/04 - Kinect :ba
Jan-Willem Wisselink
Veteraan
Veteraan
Netherlands
Berichten: 310
Lid geworden op: 27 jul 2017, 14:10
Locatie: Utrecht
Merk SP: Stromer ST1x
Km-stand: 20.000

Fijn dat het gelukt is! Met de oude methodiek? Of met de nieuwe 'refresh' tokens? Want dan is je nieuwe script natuurlijk erg interessant.
Stromer ST1x rijder sinds augustus 2017. 2x983Wh. Thudbuster large. Continental Top Contact. Ergo / Spirgrips. Woonwerk: 58km enkel / 116km retour.
Gebruikersavatar
JeroenSpock
Expert
Expert
Netherlands
Berichten: 156
Lid geworden op: 22 jun 2020, 22:50
Locatie: Veendam
Merk SP: ST3 L 2020
Km-stand: 11166

OlivierVK schreef: 09 nov 2021, 09:52 Ik heb het werkend gekregen :d3:

Via sniffing gezien dat - op basis van de app zoals geinstalleerd op mijn iphone - de API calls verschillend zijn.
Ik heb een python scriptje gemaakt dat nu correct werkt.

Ik zal de details later dit weekend posten voor de geïnteresseerden.

Groetjes
Ja kom maar op met de details, ik wil ook nog steeds een keer gaan meegluren met de app om zo wat gegevens uit te lezen.
Stromer ST3 L - MY202 - 983Wh SoH 99% - Wren voorvork - Bodyfloat zadelpen - SQ Labs Innerbarends - Aptonia ligstuur - 2 x Ortlieb backroller pine
Je hebt pas een Mercedes als er 40 mensen in kunnen
Arjannv
Rookie
Rookie
Netherlands
Berichten: 9
Lid geworden op: 28 apr 2021, 15:21
Merk SP: Stromer ST2

Ik heb al een tijdje ook een Python scriptje draaien om de waardes van Stromer te loggen in Traccar.
Hiermee maak ik nog geen gebruik van de refresh tokens, wel logged ie niet elke keer opnieuw in.
Misschien dat ie handig is voor iemand :)

Code: Selecteer alles

import requests
import urllib
import datetime
import json
import time
import math
from geopy.geocoders import Nominatim

password = "StromerAccountPassword"
username = "StromerAccountEmail"
client_id = "APP client_id"
client_secret = "APP client_secret"

traccar_url = 'http://10.0.0.6:5055/'  #IP and Port of Traccar
traccar_id = 102
unavailableTime = 30 #All locations below this time limit in minutes will be accepted and passed to Traccar
scan_interval = 2*60 #Interval of script in seconds
sleepDuration = 0
refreshInterval = 30*60 #interval in seconds

def get_code(client_id, username, password):
	MAX_RETRIES = 20
	session = requests.session()
	adapter = requests.adapters.HTTPAdapter(max_retries=MAX_RETRIES)
	session.mount('https://', adapter)
	session.mount('http://', adapter)
	url = "https://api3.stromer-portal.ch/users/login/"
	s = requests.session()
	res = s.get(url)
	s.cookies

	qs = urllib.parse.urlencode({
		"client_id":
		client_id,
		"response_type":
		"code",
		"redirect_url":
		"stromerauth://auth",
		"scope":
		"bikeposition bikestatus bikeconfiguration bikelock biketheft bikedata bikepin bikeblink userprofile",
	})

	data = {
		"password": password,
		"username": username,
		"csrfmiddlewaretoken": s.cookies.get("csrftoken"),
		"next": "/o/authorize/?" + qs,
	}

	res = s.post(url, data=data, headers=dict(Referer=url), allow_redirects=False)
	res = s.send(res.next, allow_redirects=False)
	qs = urllib.parse.urlparse(res.headers["Location"])
	code  = qs.query.rsplit("=", 1)[1]
	return code


def get_access_token(client_id, client_secret, code):
	url = "https://api3.stromer-portal.ch//o/token/"
	params = {
		"grant_type": "authorization_code",
		"client_id": client_id,
		"client_secret": client_secret,
		"code": code,
		"redirect_uri": "stromerauth://auth",
	}

	res = requests.post(url, params=params)
	return res.json()["access_token"]


def call_api(access_token, endpoint, params={}):
	url = "https://api3.stromer-portal.ch/rapi/mobile/v2/%s" % endpoint
	headers = {"Authorization": "Bearer %s" % access_token}
	res = requests.get(url, headers=headers, params={})
	data = res.json()["data"]
	if isinstance(data, list):
		return data[0]
	else:
		return data


def call_bike(access_token, bike, endpoint, cached="false"):
	endpoint = 'bike/%s/%s' % (bike["bikeid"], endpoint)
	params = {'cached': '%s' % cached}
	state = call_api(access_token, endpoint, params)
	return state

def distanceCalc(lat1, lon1, lat2, lon2):
	R = 6364.611 # Radius of earth in km @ 52.8*

	lat1 = math.radians(lat1)
	lon1 = math.radians(lon1)
	lat2 = math.radians(lat2)
	lon2 = math.radians(lon2)
	dlon = lon2 - lon1
	dlat = lat2 - lat1

	a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
	c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

	distance = R * c * 1000 #distance in meters
	return distance
	
#UTC offset
is_dst = time.daylight and time.localtime().tm_isdst > 0
utc_offset = - (time.altzone if is_dst else time.timezone)

#Variables
prevLat = -1
prevLon = -1
prevTime = -1
prevSoC = -1
prevSpeed = -1
geolocator = Nominatim(user_agent="FindMyStromer")

print("Script for Stromer bike location")
code = get_code(client_id, username, password)
access_token = get_access_token(client_id, client_secret, code)

while True:
	starttime = int(time.time())
	
	#Check if it a uncached version needs to be requested
	cache = "true"
	if ((starttime - prevTime) > (refreshInterval + 1)): 
		cache = "false"
	
	try:
		bike = call_api(access_token, "bike")
		state = call_bike(access_token, bike, 'state/', cache)
		position = call_bike(access_token, bike, 'position/', cache)
		#print(state)
		#print(position)
		#print(call_bike(access_token, bike, 'service_info/', cache))
		#print(call_bike(access_token, bike, 'bike/statistics/all/', cache))
		#print(call_bike(access_token, bike, 'bike/statistics/extra_data/', cache))
	except:
		code = get_code(client_id, username, password)
		access_token = get_access_token(client_id, client_secret, code)
		bike = call_api(access_token, "bike")
		state = call_bike(access_token, bike, 'state/', cache)
		position = call_bike(access_token, bike, 'position/', cache)
	
	location_time = state['rcvts']
	now = int(time.time())
	if (location_time >= now):
		location_time = location_time - utc_offset
	try: 
		location_adress = str(geolocator.reverse(str(position['latitude']) + ', ' + str(position['longitude'])))
	except:
		location_adress = 'Onbekend'

	print("Bike is at " + location_adress + " (" + str(position['latitude']) + ', ' + str(position['longitude']) + " at " + str(position['altitude']) + "m)" + " with " + str(state['battery_SOC']) + "% SoC")

	#Post to Traccar
	validLocation = True
	if (prevLat != -1) and (prevLon != -1):
		prevDistance = distanceCalc(position['latitude'], position['longitude'], prevLat, prevLon) #in meter
		try:
			prevSpeed = (prevDistance / (location_time - prevTime)) * 3.6 #in km/h
		except:
			prevSpeed = 0
		
		if (prevSpeed > 80):
			validLocation = False
	newData = True
	if validLocation:
		if (position['latitude'] == prevLat) and (position['longitude'] == prevLon) and (prevSoC == state['battery_SOC']) and (prevSpeed == state['bike_speed']):
			newData = False
		prevLat = position['latitude']
		prevLon = position['longitude']
		prevTime = location_time
		prevSoC == state['battery_SOC']
		prevSpeed = state['bike_speed']
	
	#if True:
	if ((now - location_time) < (sleepDuration + 1)) and validLocation and newData: 
		traccar_post = traccar_url + '?id=' + str(traccar_id) + '&lat=' + str(position['latitude']) + '&lon=' + str(position['longitude']) + '&timestamp=' + str(location_time) + '&hdop=2&altitude=' + str(position['altitude']) + '&speed=' + str(state['bike_speed']/1.8) + '&accuracy=' + str(int(123)) + '&batt=' + str(state['battery_SOC']) + '&address=' + location_adress + "&batteryhealth=" + str(state['battery_health']) + "&assist=" + str(state['assistance_level']) + "&totalenergy=" + str(state['total_energy_consumption']) + "&tempbattery=" + str(state['battery_temp']) + "&tempmotor=" + str(state['motor_temp']) + "&locked=" + str(state['lock_flag']) + "&powercycles=" + str(state['power_on_cycles']) + "&totaldistance=" + str(state['total_distance'])
		traccar_response = requests.get(traccar_post)
	else:
		print("Old!")
	
	sleepDuration = scan_interval  - (now - starttime)
	print(str(int(sleepDuration)) + ' seconden wachten')
	time.sleep(sleepDuration)
Falafel
Rookie
Rookie
Netherlands
Berichten: 5
Lid geworden op: 15 nov 2021, 19:21
Locatie: Nederland
Merk SP: Stromer
Km-stand: 420

Hoi allemaal, ik probeer al even om data uit te lezen zoals anderen hier doen maar het lukt me niet. Ik heb de client_id en client_secret weten te bemachtigen, maar de hier geposte scripts geven me geen data terug. Voor de volledigheid post ik mijn script hieronder, het is in feite hetzelfde als verschillende andere scripts die eerder in dit topic zijn gepost:

Code: Selecteer alles

# !/usr/bin/env python3
import requests
import urllib
import datetime
import json
import time
import math

password = "geheim"
username = "geheim"
client_id = "geheim"
client_secret = "geheim"


def get_code(client_id, username, password):
    MAX_RETRIES = 20
    session = requests.session()
    adapter = requests.adapters.HTTPAdapter(max_retries=MAX_RETRIES)
    session.mount('https://', adapter)
    session.mount('http://', adapter)
    url = "https://api3.stromer-portal.ch/users/login/"
    s = requests.session()
    res = s.get(url)
    s.cookies

    qs = urllib.parse.urlencode({
        "client_id":
            client_id,
        "response_type":
            "code",
        "redirect_url":
            "stromerauth://auth",
        "scope":
            "bikeposition bikestatus bikeconfiguration bikelock biketheft bikedata bikepin bikeblink userprofile",
    })

    data = {
        "password": password,
        "username": username,
        "csrfmiddlewaretoken": s.cookies.get("csrftoken"),
        "next": "/o/authorize/?" + qs,
    }

    res = s.post(url, data=data, headers=dict(Referer=url), allow_redirects=False)
    res = s.send(res.next, allow_redirects=False)
    qs = urllib.parse.urlparse(res.headers["Location"])
    code = qs.query.rsplit("=", 1)[1]
    return code


def get_access_token(client_id, client_secret, code):
    url = "https://api3.stromer-portal.ch//o/token/"
    params = {
        "grant_type": "authorization_code",
        "client_id": client_id,
        "client_secret": client_secret,
        "code": code,
        "redirect_uri": "stromerauth://auth",
    }

    res = requests.post(url, params=params)
    return res.json()["access_token"]


def call_api(access_token, endpoint, params={}):
    url = "https://api3.stromer-portal.ch/rapi/mobile/v2/%s" % endpoint
    headers = {"Authorization": "Bearer %s" % access_token}
    res = requests.get(url, headers=headers, params={})
    data = res.json()["data"]
    if isinstance(data, list):
        return data[0]
    else:
        return data


def call_bike(access_token, bike, endpoint, cached="false"):
    endpoint = 'bike/%s/%s' % (bike["bikeid"], endpoint)
    params = {'cached': '%s' % cached}
    state = call_api(access_token, endpoint, params)
    return state


code = get_code(client_id, username, password)
access_token = get_access_token(client_id, client_secret, code)
bike = call_api(access_token, "bike")
state = call_bike(access_token, bike, 'state/')
De error die ik krijg luidt "IndexError: list index out of range" aan het einde van de call_api functie; de error ontstaat omdat er geen data terugkomt. Heeft iemand een idee wat ik hier fout doe, of een idee om het probleem op te sporen? Het lijkt namelijk dat hetzelfde script voor anderen wel werkt. E-mail, wachtwoord, client_id en client_secret heb ik allemaal dubbelgecheckt en kunnen eigenlijk niet missen. De eerste twee functies (get_code en get_access_token) geven allebei wel een resultaat.
Arjannv
Rookie
Rookie
Netherlands
Berichten: 9
Lid geworden op: 28 apr 2021, 15:21
Merk SP: Stromer ST2

Kan je checken of je een goede access_token binnen hebt gekregen?
Ik kreeg er een met 30 tekens.

Misschien ook even checken wat het resultaat is van res?
Na deze regel:

Code: Selecteer alles

res = requests.get(url, headers=headers, params={})
Falafel
Rookie
Rookie
Netherlands
Berichten: 5
Lid geworden op: 15 nov 2021, 19:21
Locatie: Nederland
Merk SP: Stromer
Km-stand: 420

Arjannv schreef: 17 nov 2021, 15:02 Kan je checken of je een goede access_token binnen hebt gekregen?
Ik kreeg er een met 30 tekens.

Misschien ook even checken wat het resultaat is van res?
Na deze regel:

Code: Selecteer alles

res = requests.get(url, headers=headers, params={})
De access_token lijkt in orde, ik krijg er inderdaad ook een van 30 tekens. Aan de response zie ik ook niets geks, afgezien van het feit dat er geen data meekomt, natuurlijk. De content in de response ziet er namelijk zo uit:

Code: Selecteer alles

b'{"result":"ok","data":[]}'
Arjannv
Rookie
Rookie
Netherlands
Berichten: 9
Lid geworden op: 28 apr 2021, 15:21
Merk SP: Stromer ST2

Falafel schreef: 20 nov 2021, 14:16

Code: Selecteer alles

b'{"result":"ok","data":[]}'
Mm, dat is gek. Bij mij is die "data" array helemaal gevuld. Kan 't dan toch dat er inmiddels nieuwe API urls zijn..?
Dat jij dan wel al op de nieuwe API gezet bent en ik nog niet volledig?
Falafel
Rookie
Rookie
Netherlands
Berichten: 5
Lid geworden op: 15 nov 2021, 19:21
Locatie: Nederland
Merk SP: Stromer
Km-stand: 420

Arjannv schreef: 23 nov 2021, 09:22
Falafel schreef: 20 nov 2021, 14:16

Code: Selecteer alles

b'{"result":"ok","data":[]}'
Mm, dat is gek. Bij mij is die "data" array helemaal gevuld. Kan 't dan toch dat er inmiddels nieuwe API urls zijn..?
Dat jij dan wel al op de nieuwe API gezet bent en ik nog niet volledig?
Het zou kunnen! Ik heb mijn fiets pas sinds begin november. Ik heb echter (nog) niet de vaardigheden/tools om die nieuwe API urls op te sporen, helaas. Ik heb nog wat meer onderzoek gedaan maar concludeer dat dat haast wel de reden moet zijn... Als ik de client_id en client_secret verander krijg ik namelijk al een 401 error bij het aanvragen van de access_token, dus die waardes moeten wel in orde zijn. Wellicht heeft iemand hier er meer verstand van... :mrgreen:
Arjannv
Rookie
Rookie
Netherlands
Berichten: 9
Lid geworden op: 28 apr 2021, 15:21
Merk SP: Stromer ST2

Ja, ik had al geprobeerd de android APK uit te pluizen en de URL's te vinden. Maar daar werd ik niet veel wijzer uit.
Zal eens proberen een MITM proxy op te zetten.. :)
Plaats reactie