Toegang krijgen tot de OMNI API

buco
Runner-up
Runner-up
Belgium
Berichten: 23
Lid geworden op: 09 feb 2022, 20:39
Locatie: Heist op den Berg
Merk SP: Stromer

Ugh, blijkbaar toch niet helemaal ... client_id heb ik gevonden. In de APK 3.1.4 vind ik oaut_* waaronder oaut_client_id. Maar helaas geen oauth_client_secret, zelfs in die file niets met secret. Ik heb ook 3.0.2 en 3.0.1 en een paar 2.9.x versies geprobeerd maar nothing doin'

Ik gebruikt jadx-gui en bij een global search begint die eerst te decompilen.

De mitm-proxy methode heb ik geprobeerd maar daarvoor zou ik mijn telefoon moeten rooten. Ook zoals eerder gezegd krijg ik de app niet meteen aan de praat op mijn macbook om dan een mitm-proxy op te zetten. :roll:
SirJohnDoe
Pro
Pro
Netherlands
Berichten: 64
Lid geworden op: 30 nov 2020, 23:32
Merk SP: Stromer ST3 2019

buco schreef: 10 feb 2022, 07:09 Ugh, blijkbaar toch niet helemaal ... client_id heb ik gevonden. In de APK 3.1.4 vind ik oaut_* waaronder oaut_client_id. Maar helaas geen oauth_client_secret, zelfs in die file niets met secret. Ik heb ook 3.0.2 en 3.0.1 en een paar 2.9.x versies geprobeerd maar nothing doin'

Ik gebruikt jadx-gui en bij een global search begint die eerst te decompilen.

De mitm-proxy methode heb ik geprobeerd maar daarvoor zou ik mijn telefoon moeten rooten. Ook zoals eerder gezegd krijg ik de app niet meteen aan de praat op mijn macbook om dan een mitm-proxy op te zetten. :roll:
Je kunt de client_secret nog steeds vinden als je APK 2.9.11 gebruikt, zoek op 'https://api3.stromer-portal.ch/'. Maar ik heb net het script voor HA ook geüpdate op basis van onderstaande, dus misschien kun je die testen, zie helemaal onderaan in deze post.
Arjannv schreef: 25 nov 2021, 15:43 OK, hebbes :)

Met de MITM proxy heb ik de volgende URLs achterhaald:

In de get_code functie wordt het:

Code: Selecteer alles

https://stromer-portal.ch/mobile/v4/login/ 
In de get_code functie moet de POST array "data->next" ook iets aangepast worden naar:

Code: Selecteer alles

"next": "/mobile/v4/o/authorize/?" + qs
In de get_access_token functie:

Code: Selecteer alles

https://stromer-portal.ch/mobile/v4/o/token/
In de get_access_token functie moet de "params" array ook iets anders:

Code: Selecteer alles

	params = {
		"grant_type": "authorization_code",
		"client_id": client_id,
		"code": code,
		"redirect_uri": "stromer://auth",
	}
In de call_api functie:

Code: Selecteer alles

"https://api3.stromer-portal.ch/rapi/mobile/v4.1/%s" % endpoint
De "client_id" is trouwens ook veranderd (beginnend met 4P). De andere, "client_secret" wordt nergens meer gebruikt.

Ik heb 'm trouwens nog niet geprobeerd, omdat alles bij mij nog werkt.
Dank je wel voor de info, ik heb het verwerkt in het python script en het werkt perfect! Hierbij voor de geïnteresseerden, je hebt dus enkel je 'client_id' meer nodig:

Code: Selecteer alles

#!/usr/bin/env python3

import requests
from urllib.parse import urlencode, parse_qs, splitquery
from datetime import datetime
import json

password = "VUL IN"
username = "VUL IN"
client_id = "HAAL UIT DECOMPILED APK"

def get_code(client_id, username, password):
    url = "https://stromer-portal.ch/mobile/v4/login/"
    s = requests.session()
    res = s.get(url)
    s.cookies

    qs = 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": "/mobile/v4/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 = splitquery(res.headers["Location"])
    code = parse_qs(qs)["code"][0]
    return code

def get_access_token(client_id, code):
    url = "https://stromer-portal.ch/mobile/v4/o/token/"
    params = {
		"grant_type": "authorization_code",
		"client_id": client_id,
		"code": code,
		"redirect_uri": "stromer://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/v4.1/%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 = "true"):
    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, code)
bike = call_api(access_token, "bike")
print('bike:', json.dumps(bike, indent=True))

state = call_bike(access_token, bike, 'state/')
print('state:', json.dumps(state, indent=True))

position = call_bike(access_token, bike, 'position/')
print('position:', json.dumps(position, indent=True))
En het Home Assistant script voor AppDaemon:

Code: Selecteer alles

#!/usr/bin/env python3
import requests
from urllib.parse import urlencode, parse_qs, splitquery
from datetime import datetime
import json
import time
import appdaemon.plugins.hass.hassapi as hass

#
# Stromer app
#

password = "VUL IN "
username = "VUL IN"
client_id = "HAAL UIT DECOMPILED APK"

class stromer(hass.Hass):
    def initialize(self):
        starttime=time.time()
        while True:
            try:
                def get_code(client_id, username, password):
                    MAX_RETRIES = 20  #!Added https://stackoverflow.com/questions/33895739/python-requests-module-error-cant-load-any-url-remote-end-closed-connection
                    session = requests.Session()
                    adapter = requests.adapters.HTTPAdapter(max_retries=MAX_RETRIES)
                    session.mount('https://', adapter)
                    session.mount('http://', adapter)
                    url = "https://stromer-portal.ch/mobile/v4/login/"
                    s = requests.session()
                    res = s.get(url)
                    s.cookies
                
                    qs = 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": "/mobile/v4/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 = splitquery(res.headers["Location"])
                    code = parse_qs(qs)["code"][0]
                    return code
                
                
                def get_access_token(client_id, code):
                    url = "https://stromer-portal.ch/mobile/v4/o/token/"
                    params = {
                        "grant_type": "authorization_code",
                        "client_id": client_id,
                        "code": code,
                        "redirect_uri": "stromer://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/v4.1/%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, code)
                bike = call_api(access_token, "bike")
                state = call_bike(access_token, bike, 'state/')
                position = call_bike(access_token, bike, 'position/')
     
                self.set_state("sensor.stromer_current_speed", state = state['bike_speed'])
                self.set_state("sensor.stromer_trip_distance", state = state['trip_distance'])
                self.set_state("sensor.stromer_avg_speed_trip", state = state['average_speed_trip'])
                self.set_state("sensor.stromer_triptime", state = state['trip_time'])
                self.set_state("sensor.stromer_battery_chargelevel", state = state['battery_SOC'])
                self.set_state("sensor.stromer_lockstate", state = state['lock_flag'])
                self.set_state("sensor.stromer_geocoded_location", state = '', attributes = {"altitude": position['altitude'], "latitude": position['latitude'], "longitude": position['longitude'], "icon": 'mdi:map'})
                self.set_state("sensor.stromer_batterytemp", state = state['battery_temp'])
                self.set_state("sensor.stromer_motortemp", state = state['motor_temp'])
                self.set_state("sensor.stromer_battery_healthstate", state = state['battery_health'])
                self.set_state("sensor.stromer_battery_poweron_cycles", state = state['power_on_cycles'])
                self.set_state("sensor.stromer_totaldistance", state = state['total_distance'])
                self.set_state("sensor.stromer_totaltime", state = state['total_time'])
                self.set_state("sensor.stromer_avg_energy", state = state['average_energy_consumption'])
                self.set_state("sensor.stromer_softwareversion", state = state['suiversion'])
                self.set_state("sensor.stromer_lastupdated", state = state['rcvts'])
                self.log("Omni data bijgewerkt")
                time.sleep(60)
            except Exception:
                self.log("ERROR")
                time.sleep(300)
                continue
buco
Runner-up
Runner-up
Belgium
Berichten: 23
Lid geworden op: 09 feb 2022, 20:39
Locatie: Heist op den Berg
Merk SP: Stromer

Aha, ik denk dat ik het gevonden heb, vielen dank!
Gebruikersavatar
s-EVE
Pro
Pro
Netherlands
Berichten: 51
Lid geworden op: 05 nov 2018, 20:11
Locatie: Midden NL
Merk SP: ST2

Excuses allen, al tijden niet op het forum geweest maar wel aan de slag geweest met Stromer en Home Assistant (ik was mijn losse scripts en zooi zat):

Mocht je avontuurlijk zijn, je API keys al hebben achterhaald en daarmee relatief makkelijk je Stromer in Home Assistant willen hebben: via HACS kun je nu https://github.com/CoMPaTech/stromer gemakkelijk toevoegen als custom_component
ST2-Wit, standaard, SP-houder, Ortlieb Office, Cratoni Commuter :ba Geen extra toeters, wel een extra bel!
Commute: ~35km midden NL - ruwweg Vianen <-> Geldermalsen - FM: Die uit de Betuwe
Gebruikersavatar
s-EVE
Pro
Pro
Netherlands
Berichten: 51
Lid geworden op: 05 nov 2018, 20:11
Locatie: Midden NL
Merk SP: ST2

Ik heb op basis van jullie feedback diverse zaken aangepast, inclusief de v4 approach (kindly borrowed from @SirJohnDoe's post above) waarbij je het client secret niet meer nodig hebt + voorbeelden hoe je sneller dan de default gezette 10 minuten updates op kunt halen. Zie https://github.com/compatech/stromer voor meer informatie
ST2-Wit, standaard, SP-houder, Ortlieb Office, Cratoni Commuter :ba Geen extra toeters, wel een extra bel!
Commute: ~35km midden NL - ruwweg Vianen <-> Geldermalsen - FM: Die uit de Betuwe
elnkosc
Rookie
Rookie
Netherlands
Berichten: 4
Lid geworden op: 01 mei 2022, 12:28
Locatie: Tilburg
Merk SP: Stromer ST3
Km-stand: 5000

Heeft iemand een overzicht van de endpoints die door de API worden ondersteund? In het begin van deze thread worden er meerdere genoemd die niet (meer) ondersteund worden. Naast bike, state, en position lijken er geen anderen te worden ondersteund.
elnkosc
Rookie
Rookie
Netherlands
Berichten: 4
Lid geworden op: 01 mei 2022, 12:28
Locatie: Tilburg
Merk SP: Stromer ST3
Km-stand: 5000

Voor degenen die het interessant vinden heb ik een volledige python package geschreven welke op PyPi beschikbaar is.
Zie https://pypi.org/project/stromer-api/1.0/
Dennis071
Rookie
Rookie
Netherlands
Berichten: 5
Lid geworden op: 05 jun 2021, 07:24
Merk SP: Stromer
Km-stand: 4000

Hoi mede Stromers,

ik lees al even dit topic mee omdat ik ook heel graag mijn Home Assistant wil uitbreiden met de data van mn ST1x.. Ik ben alleen geen hardcore techneut en loop dus al even vast bij het achterhalen van de clientID op mn macbook (in combi met de app op mn iPhone).

Ik heb MITM geinstalleerd en zie op zich wel data voorbij komen, maar ik weet niet zo goed waar ik moet kijken...:-)

Is er iemand die me een klein duwtje in de goeie richting zou willen geven??

Thanks in advance!
Rost
Rookie
Rookie
Netherlands
Berichten: 3
Lid geworden op: 28 jul 2022, 23:35
Locatie: '22 ST3 Wit
Merk SP: Stromer

FWIW; ben een flinke tijd aan het puzzelen geweest, vooral om veel info aan de oude kant was. Het is me uiteindelijk gelukt.
De weg via mitmproxy (of andere sniffers) werkt niet meer, omdat de app z'n client_id en secret niet meer via de requests lijkt door te geven.

Anyway, samengevat:
Doel was om m'n nieuwe ST3 (Yay!) in Home-Assistant te krijgen met de erg mooie integratie van gebruiker @s-EVE. (Thanks!)
Stappen:
- Via FX-File Explorer de APK geexporteerd en op m'n PC gezet.
- Met APKToolGUI alle bestanden uitgepakt.
- Notepad++ gebruikt (Search->Find in Files) om de oauth_client_id te vinden, deze staat bij mij in res\values\strings.xml.

Met de username, password en deze client_id lukt het me dan om de HA-Integratie aan de praat te krijgen.
Bert20
Pro
Pro
Netherlands
Berichten: 95
Lid geworden op: 13 jun 2022, 19:11
Merk SP: Stromer ST3
Km-stand: 5000

Interessant onderwerp.
Kan je bijvoorbeeld met uitlezen of de Stromer aan het laden is en zo ja wanneer de batterij 100% is, een schakelaar bedienen om de lader uit te schakelen?
Plaats reactie