]> git.giorgioravera.it Git - mercedes_me_api.git/commitdiff
Added Python version of the script.
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Thu, 22 Oct 2020 11:33:59 +0000 (13:33 +0200)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Thu, 22 Oct 2020 11:33:59 +0000 (13:33 +0200)
.gitignore
README.md
mercedes_me_api.py [new file with mode: 0644]
mercedes_me_api.sh

index f76a4bd36eb9507cd6d663ad78c232fcfcf8b7c6..2ef5a3b1bfb847331bf0c6036c2db89dd020c8d2 100644 (file)
@@ -1,2 +1,3 @@
-.mercedes_credentials
-.mercedes_token
+.mercedesme_credentials
+.mercedesme_token
+.mercedesme_resources
index 69cd9ded58dd797cd841ce47cbb9998d1663be1e..7d3084bcc4179786d96851a909edb3e8494bfed7 100644 (file)
--- a/README.md
+++ b/README.md
@@ -16,14 +16,28 @@ To use this script it's necessary to perform the following instructions:
 1) clone the repository
 2) create a credentials files (.mercedes_credentials) with:
 ```bash
-CLIENT_ID=""
-CLIENT_SECRET=""
-VEHICLE_ID=""
+CLIENT_ID=<**INSERT_YOUR_CLIENT_ID**>
+CLIENT_SECRET=<**INSERT_YOUR_CLIENT_SECRET**>
+VEHICLE_ID=<**INSERT_YOUR_VEHICLE_ID**>
 ```
 
 where CLIENT_ID and CLIENT_SECRET referring to the application information that can be found in [Mercedes Developer Console](https://developer.mercedes-benz.com/console) and VEHICLE_ID is the VIN of your car.
 
-## Usage
+## Python Usage
+To execute the script read below:
+```bash
+usage: mercedes_me_api.py [-h] [-t] [-r] [-s] [-R] [-v]
+
+optional arguments:
+  -h, --help       show this help message and exit
+  -t, --token      Procedure to obtatin the Access Token
+  -r, --refresh    Procedure to refresh the Access Token
+  -s, --status     Retrive the Status of your Vehicle
+  -R, --resources  Retrive the list of available resources of your Vehicle
+  -v, --version    show program's version number and exit
+```
+
+## Bash Usage
 To execute the script read below:
 ```bash
 Usage:    mercedes_me_api.sh <arguments>
@@ -38,6 +52,7 @@ The possible arguments are:
     -l, --lock         Retrive the Lock Status of your Vehicle
     -s, --status       Retrive the General Status of your Vehicle
 ```
+
 ## License
 [MIT](http://opensource.org/licenses/MIT) © Giorgio Ravera
 
diff --git a/mercedes_me_api.py b/mercedes_me_api.py
new file mode 100644 (file)
index 0000000..5f0c220
--- /dev/null
@@ -0,0 +1,375 @@
+"""
+Author: G. Ravera
+Version 0.1
+Creation Date: 22/10/2020
+
+Change log:
+            22/10/2020 - 0.1 - First Issue
+"""
+import argparse
+import base64
+from configobj import ConfigObj
+import getopt
+import logging
+import json
+import os
+import requests
+import sys
+
+# URLs
+oauth_url = "https://id.mercedes-benz.com/as/token.oauth2"
+res_url_prefix = "https://api.mercedes-benz.com/vehicledata/v2"
+# File Parameters
+CONF_CLIENT_ID = "CLIENT_ID"
+CONF_CLIENT_SECRET = "CLIENT_SECRET"
+CONF_VEHICLE_ID = "VEHICLE_ID"
+# Config Parameters
+MercedeMeConfig = { 
+            "name": "Mercedes Me API",
+            "domain": "mercedesmeapi",
+            "version": "0.1",
+            "token_file": ".mercedesme_token",
+            "credentials_file": ".mercedesme_credentials",
+            "available_resources_file": ".mercedesme_resources",
+            "redirect_uri": "https://localhost",
+            "scope": "mb:vehicle:mbdata:fuelstatus%20mb:vehicle:mbdata:vehiclestatus%20mb:vehicle:mbdata:vehiclelock%20offline_access",
+            "client_id": "",
+            "client_secret": "",
+            "vin": "",
+            "base64": "",
+            "access_token": "",
+            "refresh_token": "",
+}
+# Available Resources (Array)
+availableResources = {}
+# Response Validity
+valid_res = False
+# Logger
+logger = logging.getLogger(__name__)
+
+########################
+# Read Configuration
+########################
+def ReadCfg():
+    global availableResources
+
+    # Read Credentials
+    if not os.path.isfile(MercedeMeConfig['credentials_file']):
+        logger.error ("Credential File " + MercedeMeConfig['credentials_file'] + " not found")
+        return False
+    f = ConfigObj(MercedeMeConfig['credentials_file'])
+    MercedeMeConfig['client_id'] = f.get(CONF_CLIENT_ID)
+    if not MercedeMeConfig['client_id']:
+        logger.error ("No "+ CONF_CLIENT_ID + " found in the configuration")
+        return False
+    MercedeMeConfig['client_secret'] = f.get(CONF_CLIENT_SECRET)
+    if not MercedeMeConfig['client_secret']:
+        logger.error ("No "+ CONF_CLIENT_SECRET + " found in the configuration")
+        return False
+    MercedeMeConfig['vin'] = f.get(CONF_VEHICLE_ID)
+    if not MercedeMeConfig['vin']:
+        logger.error ("No "+ CONF_VEHICLE_ID + " found in the configuration")
+        return False
+    b64_str = MercedeMeConfig['client_id'] + ":" + MercedeMeConfig['client_secret']
+    b64_bytes = base64.b64encode( b64_str.encode('ascii') )
+    MercedeMeConfig['base64'] = b64_bytes.decode('ascii')
+
+    # Read Token
+    if not os.path.isfile(MercedeMeConfig['token_file']):
+        logger.error ("Token File missing - Creating a new one")
+        token = CreateToken()
+        if token == None:
+            logger.error ("Error creating token")
+            return False
+    else:
+        with open(MercedeMeConfig['token_file'], 'r') as file:
+            try:
+                token = json.load(file)
+            except ValueError:
+                token = {}
+            if not CheckToken(token):
+                logger.error ("Token File not correct - Creating a new one")
+                token = CreateToken()
+                if token == None:
+                    return False
+    # Save Token
+    MercedeMeConfig['access_token'] = token['access_token']
+    MercedeMeConfig['refresh_token'] = token['refresh_token']
+
+    # Get Resources List
+    if not os.path.isfile(MercedeMeConfig['available_resources_file']):
+        logger.error ("Resource File missing - Creating a new one")
+        return CreateResourcesFile()
+    else:
+        with open(MercedeMeConfig['available_resources_file'], 'r') as file:
+            try:
+                availableResources = json.load(file)
+            except ValueError:
+                availableResources = {}
+            if not CheckResources(availableResources):
+                logger.error ("Resource File not correct - Creating a new one")
+                return CreateResourcesFile()
+
+    return True
+
+########################
+# Write Token
+########################
+def WriteToken(token):
+    with open(MercedeMeConfig['token_file'], 'w') as file:
+        json.dump(token, file)
+
+########################
+# Check Token
+########################
+def CheckToken(token):
+    if "error" in token:
+        if "error_description" in token:
+            logger.error ("Error retriving token: " + token["error_description"])
+        else:
+            logger.error ("Error retriving token: " + token["error"])
+        return False
+    if len(token) == 0:
+        logger.error ("Empty token found.")
+        return False
+    if not 'access_token' in token:
+        logger.error ("Access token not present.")
+        return False
+    if not 'refresh_token' in token:
+        logger.error ("Refresh token not present.")
+        return False
+
+    return True
+
+########################
+# Create Token
+########################
+def CreateToken():
+    print( "Open the browser and insert this link:\n" )
+    print( "https://id.mercedes-benz.com/as/authorization.oauth2?response_type=code&client_id=" + MercedeMeConfig["client_id"] + "&redirect_uri=" + MercedeMeConfig["redirect_uri"] + "&scope=" + MercedeMeConfig["scope"] + "\n")
+    print( "Copy the code in the url:")
+    auth_code = input()
+
+    headers = {
+        "Authorization": "Basic " + MercedeMeConfig['base64'],
+        "content-type": "application/x-www-form-urlencoded"
+    }
+    data = "grant_type=authorization_code&code=" + auth_code + "&redirect_uri=" + MercedeMeConfig["redirect_uri"]
+
+    res = requests.post(oauth_url, data = data, headers = headers)
+    try:
+        token = res.json()
+    except ValueError:
+        logger.error ("Error retriving token " + str(res.status_code))
+        return None
+
+    # Check Token
+    if not CheckToken(token):
+        return None
+    else:
+        WriteToken(token)
+        return token
+
+########################
+# Refresh Token
+########################
+def RefreshToken():
+    headers = {
+        "Authorization": "Basic " + MercedeMeConfig['base64'],
+        "content-type": "application/x-www-form-urlencoded"
+    }
+    data = "grant_type=refresh_token&refresh_token=" + MercedeMeConfig['refresh_token']
+
+    res = requests.post(oauth_url, data = data, headers = headers)
+    try:
+        token = res.json()
+    except ValueError:
+        logger.error ("Error refreshing token: " + str(res.status_code))
+        return False
+
+    MercedeMeConfig['access_token'] = token['access_token']
+    MercedeMeConfig['refresh_token'] = token['refresh_token']
+
+    WriteToken(token)
+
+    return True
+
+########################
+# Init Resources
+########################
+def InitResources():
+    # Create Empty Status
+    for res in availableResources:
+        res["status"] = ""
+        res["timestamp"] = 0
+        res["valid"] = False
+
+########################
+# Write Resources File
+########################
+def WriteResourcesFile():
+    with open(MercedeMeConfig['available_resources_file'], 'w') as file:
+        json.dump(availableResources, file)
+
+########################
+# Check Resources
+########################
+def CheckResources(resources):
+    if "error" in resources:
+        if "error_description" in resources:
+            logger.error ("Error retriving resources: " + resources["error_description"])
+        else:
+            logger.error ("Error retriving resources: " + resources["error"])
+        return False
+    if len(resources) == 0:
+        logger.error ("Empty resources found.")
+        return False
+
+    return True
+
+########################
+# Create Resources File
+########################
+def CreateResourcesFile():
+    global availableResources
+    resName = "resources"
+    resURL = res_url_prefix + "/vehicles/" + MercedeMeConfig['vin'] + "/" + resName
+    availableResources = GetResource(resName, resURL)
+    if valid_res:
+        InitResources()
+        WriteResourcesFile()
+        return True
+    else:
+        logger.error ("Error retriving available resources")
+        logger.error ("-> " + availableResources["reason"] + " (" + str(availableResources["code"]) + ")")
+        return False
+
+########################
+# GetResource
+########################
+def GetResource(resourceName, resourceURL):
+    global valid_res
+
+    # Set Header
+    headers = {
+        "accept": "application/json;charset=utf-8", 
+        "authorization": "Bearer "+MercedeMeConfig['access_token']
+    }
+
+    # Send Request
+    res = requests.get(resourceURL, headers=headers)
+    try:
+        data = res.json()
+        valid_res = True
+    except ValueError:
+        data = { "reason": "No Data",
+                 "code" : res.status_code 
+        }
+        valid_res = False
+
+    # Check Error
+    if not res.ok:
+        if ("reason" in data):
+            reason = data["reason"]
+        else:
+            if res.status_code == 400:
+                reason = "Bad Request"
+            elif res.status_code == 401:
+                reason = "Invalid or missing authorization in header"
+            elif res.status_code == 402:
+                reason = "Payment required"
+            elif res.status_code == 403:
+                reason = "Forbidden"
+            elif res.status_code == 404:
+                reason = "Page not found"
+            elif res.status_code == 429:
+                reason = "The service received too many requests in a given amount of time"
+            elif res.status_code == 500:
+                reason = "An error occurred on the server side"
+            elif res.status_code == 503:
+                reason = "The server is unable to service the request due to a temporary unavailability condition"
+            else:
+                reason = "Generic Error"
+        data["reason"] = reason
+        data["code"] = res.status_code
+        valid_res = False
+    return data
+
+########################
+# Print Available Resources
+########################
+def PrintAvailableResources():
+    print ("Found %d resources" % len(availableResources) + ":")
+    for res in availableResources:
+        print (res["name"] + ": " + res_url_prefix + res["href"])
+
+########################
+# Print Resources Status
+########################
+def PrintResourcesStatus(valid = True):
+    for res in availableResources:
+        if((not valid) | res["valid"]):
+            print (res["name"] + ":")
+            print ("\tvalid: " + str(res["valid"]))
+            print ("\tstatus: " + res["status"])
+            print ("\ttimestamp: " + str(res["timestamp"]))
+
+########################
+# Update Resources Status
+########################
+def UpdateResourcesStatus():
+    for res in availableResources:
+        resName = res["name"]
+        resURL = res_url_prefix + res["href"]
+        result = GetResource(resName, resURL)
+        if valid_res:
+            res["valid"] = True
+            res["timestamp"] = result[resName]["timestamp"]
+            res["status"] = result[resName]["value"]
+    # Write Resource File
+    WriteResourcesFile()
+
+########################
+# Parse Input
+########################
+def ParseInput():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-t', '--token', action='store_true', help="Procedure to obtatin the Access Token")
+    parser.add_argument('-r', '--refresh', action='store_true', help="Procedure to refresh the Access Token")
+    parser.add_argument('-s', '--status', action='store_true', help="Retrive the Status of your Vehicle")
+    parser.add_argument('-R', '--resources', action='store_true', help="Retrive the list of available resources of your Vehicle")
+    parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + MercedeMeConfig['version'])
+
+    if len(sys.argv)==1:
+        parser.print_help(sys.stderr)
+        exit(1)
+
+    return parser.parse_args()
+
+########################
+# Main
+########################
+if __name__ == "__main__":
+    args = ParseInput()
+
+    # Reading Configuration
+    if not ReadCfg():
+        logger.error ("Error reading configuration")
+        exit (1)
+
+    if ( args.token == True):
+        if not CreateToken():
+            logger.error ("Error creating token")
+            exit (1)
+
+    if (args.refresh == True):
+        if not RefreshToken():
+            logger.error ("Error refreshing token")
+            exit (1)
+            
+    if (args.resources):
+        PrintAvailableResources()
+
+    if (args.status == True):
+        UpdateResourcesStatus()
+        PrintResourcesStatus()
index 5941877ad11776efc47c43d2f2e00e7f724c762a..01b4dcba01364ea5735bbe952924c374955152e6 100755 (executable)
@@ -16,8 +16,8 @@ VERSION="0.1"
 REDIRECT_URL="https://localhost"
 SCOPE="mb:vehicle:mbdata:fuelstatus mb:vehicle:mbdata:vehiclestatus mb:vehicle:mbdata:vehiclelock offline_access"
 STATE="12345678"
-TOKEN_FILE=".mercedes_token"
-CREDENTIALS_FILE=".mercedes_credentials"
+TOKEN_FILE=".mercedesme_token"
+CREDENTIALS_FILE=".mercedesmw_credentials"
 
 # Credentials
 CLIENT_ID=""