So,
ich habe eine prototypische Lösung, die für mich funktioniert, und die ich hier gern teilen möchte, in der Hoffnung, dass sie dem ein oder anderen nützt. Auf jeden Fall ist sie kostenlos. Unten findet Ihr die Anleitung und meine Scripte. Jetzt werde ich erstmal beobachten, wie sich das im Dauerbetrieb bewährt.
Ich freue mich auf Eure Rückmeldungen und darüber, wie flexibel man die openWB selber erweitern kann!
VG,
Ralf
Voraussetzung:
Mercedes Me Account muss vorhanden sein und ein Fahrzeug muss damit verbunden sein. Dort kann man auch die FIN raussuchen.
Die folgenden Schritte in der Reihenfolge durchführen.
1.
https://developer.mercedes-benz.com/
dort mit dem Mercedes Me Account einloggen
2.
https://developer.mercedes-benz.com/pro ... cle_status
> Get Access
> Bring Your Own Car (ist kostenlos für das eigene Fahrzeug)
> Package "Standard", man kann eh nichts anderes auswählen --> Next
> Continue to create a new "App" --> Next
> Create Application
> Application Name: Hier kann ein beliebiger Name stehen. Z.B.: "Fritzis E-Auto Abfrage"
> Die anderen Felder kann man leer lassen
> Anschließend findet man unter "Console" folgendes:
> App ID (hier nicht weiter benötigt)
> Client ID
> Client Secret
> Redirect URLs --> Habe ich geändert zu
http://localhost/ (mit Slash am Ende, k.A. ob nötig, muss aber später zum Request passen, sonst kommt ein Fehler)
Das war es
Jetzt kommen die Scripte zum Einsatz, diese habe ich alle nach
kopiert.
a)
authorize.py - muss man im Idealfall nur einmal aufrufen. Es "besorgt" den ersten access token und den refresh token. Danach läuft alles automatisch. Wenn mit den Tokens doch mal was schief geht, kann man hiermit immer wieder eine neue OAuth2 Autoriserung starten.
Code: Alles auswählen
# thanks to https://developer.byu.edu/docs/consume-api/use-api/oauth-20/oauth-20-python-sample-code
"""
this file should be called in an interactive shell, as it generates output and expects user input.
It needs to be called only once at the beginning (and in cases where the refresh token has expired)
"""
import requests, json
from myAppDetails import authorize_url, token_url, callback_uri, client_id, client_secret
# simulate a request from a browser on the authorize_url - will return an authorization code after the user is
# prompted for credentials. The code can be found in the address line of the browser
authorization_redirect_url = authorize_url + '?response_type=code&client_id=' + client_id + '&redirect_uri=' + callback_uri + '&scope=mb:vehicle:mbdata:evstatus'
print("go to the following url on the browser and enter the code from the returned url: ")
print("--- " + authorization_redirect_url + " ---")
authorization_code = input('code: ')
# turn the authorization code into a access token, etc
data = {'grant_type': 'authorization_code', 'code': authorization_code, 'redirect_uri': callback_uri}
print("requesting access token")
access_token_response = requests.post(token_url, data=data, verify=True, allow_redirects=False, auth=(client_id, client_secret))
print("response")
print(access_token_response.headers)
print('body: ' + access_token_response.text)
# we can now use the access_token as much as we want to access protected resources.
# Storing the access token in a file.
tokens = json.loads(access_token_response.text)
access_token = tokens['access_token']
refresh_token = tokens['refresh_token']
outFile = open('access_token','w')
outFile.write(access_token)
outFile.close()
outFile = open('refresh_token','w')
outFile.write(refresh_token)
outFile.close()
print("stored access token: " + access_token)
print("stored refresh token: " + refresh_token)
b)
mercedessoc.py - fragt den Ladestand und die Reichweite ab und schreibt die Ergebnisse nach /var/www/html/openWB/ramdisk/mymercedes_soc und mymercedes_range (nur so). Mache ich per cron alle 5 min
Code: Alles auswählen
# thanks to https://developer.byu.edu/docs/consume-api/use-api/oauth-20/oauth-20-python-sample-code
"""
This file should be called in regular intervals, but not too often
to not exceed the budget of free API calls included with the BYOCAR
subscription.
"""
import requests
import json
from myAppDetails import test_api_url, logging
# read currently active access token from disk
inFile = open('access_token','r')
access_token = inFile.read().rstrip()
inFile.close()
logging.debug('read access token from disk: %s' % access_token)
# prepare and execute API call
api_call_headers = {'Authorization': 'Bearer ' + access_token}
api_call_response = requests.get(test_api_url, headers=api_call_headers, verify=True)
logging.debug('received Mercedes API response: %s' % api_call_response.text)
# parse response to extract SOC and Range (not needed for now)
# Example response: [{"soc":{"value":"100","timestamp":1596019475000}},{"rangeelectric":{"value":"37","timestamp":1596019751000}}]
# NOTE: the order may vary. Search for the list entry with
# according to API documentation, response will stay empty and status code 204 when no updated data is available
if api_call_response.status_code not in (200, 204):
# error logging
logging.error("Failed to retrieve Mercedes vehicle status. Received:\nStatus Code: %d\nResponse: %s" % api_call_response.text)
exit(1)
elif api_call_response.status_code ==204:
# exception,not critical
if len(api_call_response.text)==0:
logging.info('Received empty response with status code 204. Vehicle did not provide update for >12h. Keeping soc unchanged.')
exit(0)
elif api_call_response.status_code == 200:
# normal parsing of result
resources = json.loads(api_call_response.text)
soc=None
range=None
for entry in resources:
if 'soc' in entry.keys():
soc = entry['soc']['value']
outFile = open('/var/www/html/openWB/ramdisk/mymercedes_soc', 'w')
outFile.write(soc)
outFile.close()
if 'rangeelectric' in entry.keys():
range = entry['rangeelectric']['value']
outFile = open('/var/www/html/openWB/ramdisk/mymercedes_range','w')
outFile.write(range)
outFile.close()
logging.info('Retrieved and stored Mercedes Vehicle Status: SOC '+soc+'%, Range '+range+'km' )
c)
refresh.py - erneuert regelmäßig den access_token, damit keine neuerliche user Authentifizierung im Browser nötig wird. Mache ich per cron alle 30 min.
Code: Alles auswählen
# thanks to https://developer.byu.edu/docs/consume-api/use-api/oauth-20/oauth-20-python-sample-code
"""
this file needs to be run to refresh an expired access token
"""
import requests
import json
from myAppDetails import token_url, client_id, client_secret, logging
inFile = open('refresh_token','r')
refresh_token = inFile.read().rstrip()
inFile.close()
# use the refresh token to get a NEW access token
data = {'grant_type': 'refresh_token', 'refresh_token': refresh_token }
logging.debug("requesting updated access token")
refresh_token_response = requests.post(token_url, data=data, verify=True, allow_redirects=False, auth=(client_id, client_secret))
# logging
logging.debug("response")
logging.debug(refresh_token_response.headers)
logging.debug('body: ' + refresh_token_response.text)
# we can now use the NEW access_token as much as we want to access the API. The token expires after 3600s, therefore we need to call the refresh action before that, e.g. every 30mins
tokens = json.loads(refresh_token_response.text)
access_token = tokens['access_token']
refresh_token = tokens['refresh_token']
# store NEW tokens in a file
outFile = open('access_token','w')
outFile.write(access_token)
outFile.close()
outFile = open('refresh_token','w')
outFile.write(refresh_token)
outFile.close()
# more logging
logging.info("stored updated access token: " + access_token)
logging.info("stored updated refresh token: " + refresh_token)
d)
myAppDetails.py - enthält gemeinsam genutzte Settings.
Hier müssen die Client ID und das Client Secret von oben eingetragen werden, ausserdem die Fahrzeug-Identifikations-Nr (FIN)
Code: Alles auswählen
"""
this files holds the configuration details
"""
# client (application) credentials - you get these when signing up for the service in the section "Console"
client_id = '<<<Your Client ID Here>>>'
client_secret = '<<<Your Client Secret Here>>>'
# this must match the setting made in the "Console"
callback_uri = "https://localhost/"
# Your Vehicle ID
FIN='<<<Your FIN Here>>>'
# required for OAuth2
authorize_url = "https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/authorize"
token_url = "https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/token"
# The actual home of the API
test_api_url = "https://api.mercedes-benz.com/vehicledata/v1/vehicles/"+FIN+"/containers/electricvehicle"
# logger settings for the other scripts
import logging
logging.basicConfig(filename='/var/www/html/openWB/modules/soc_mercedes/app.log',level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p')
3. Eintragen der persönlichen Settings in d)
4. Einmalige Autorisierung mit script a)
> Das Script ausführen
cd /var/www/html/openWB/modules/soc_mercedes/ && python3 /var/www/html/openWB/modules/soc_mercedes/authorize.py
> Die auf der Konsole angezeigte URL in ein Browserfenster pasten
> Die Mercedes Me Seite sollte erscheinen und ggfs Login sowie Consent zur Weitergabe der Daten an "Fritzis E-Auto Abfrage" abfragen
> Anschließend ruft sie einen callback auf localhost auf, der steht dann in der Browser Adresszeile, da die URL nicht aufgelöst werden kann
> In der Addresszeile des Browsers steht dann sowas wie
Code: Alles auswählen
https://localhost/?code=614d8e2f-2cd0-496f-9fd6-c79b6fa2a1d0
> Der Code ist der Access Grant. Den Code kopieren und an der Eingabeaufforderung auf der Konsole eingeben
> Das Script fragt dann mit dem o.g. Grant den Access Token und den Refresh Token an und speichert diese lokal
5. Scripte b) und c) in die crontab einbinden. Sieht bei mir so aus:
Code: Alles auswählen
1 0 * * * /var/www/html/openWB/runs/cronnightly.sh >> /var/log/openWB.log 2>&1
*/5 * * * * cd /var/www/html/openWB/modules/soc_mercedes/ && python3 /var/www/html/openWB/modules/soc_mercedes/mercedessoc.py && /var/www/html/openWB/runs/cron5min.sh >> /var/log/openWB.log 2>&1
@reboot /var/www/html/openWB/runs/atreboot.sh >> /var/log/openWB.log 2>&1
*/30 * * * * cd /var/www/html/openWB/modules/soc_mercedes/ && python3 /var/www/html/openWB/modules/soc_mercedes/refresh.py
* * * * * /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1
* * * * * sleep 10 && /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1
* * * * * sleep 20 && /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1
* * * * * sleep 30 && /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1
* * * * * sleep 40 && /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1
* * * * * sleep 50 && /var/www/html/openWB/regel.sh >> /var/log/openWB.log 2>&1
6. In den openWB Einstellungen unter Modulkonfiguration das SoC HTTP Modul einstellen. URL ist
Das war's! Ich hoffe, dass die Anleitung nachvollziehbar ist.
Die Scripte loggen nach /var/www/html/openWB/modules/soc_mercedes/app.log . Optional kann man noch in myAppDetails.py das loglevel von logging.DEBUG auf logging.INFO umstellen, wenn alles läuft.
PS: sorry für die englischen Kommentare und Ausgaben. Berufskrankheit. Ist mir erst nachher aufgefallen, dass hier alles auf Deutsch ist.