Skip to content

Updated Python library to latest API version #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,7 @@ dmypy.json
# profiling data
.prof

# Temp debug files
fordpass/demo.py

# End of https://www.toptal.com/developers/gitignore/api/python
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ It's more or less a straight port of @d4v3y0rk's NPM module [d4v3y0rk/ffpass](ht
* Stop the engine (if supported)
* Lock the doors
* Unlock the doors
* Poll the car for an update
* Save Token to file to be reused (Speeds up the Ford API a lot and prevents timeouts when tokens expire)

## Install
Install using pip:
Expand All @@ -27,13 +29,13 @@ pip install fordpass
To test the libary there is a demo script `demo.py`.

```
demo.py USERNAME PASSWORD VIN
demo.py USERNAME PASSWORD VIN 1
```

e.g.

```
demo.py test@test.com mypassword WX12345678901234
demo.py test@test.com mypassword WX12345678901234 1(True of false to save token in a file for reuse)
```

## Publishing new versions of this package
Expand Down
8 changes: 4 additions & 4 deletions fordpass/bin/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

if __name__ == "__main__":

if len(sys.argv) != 4:
if len(sys.argv) < 4:
raise Exception('Must specify Username, Password and VIN as arguments, e.g. demo.py test@test.com password123 WX231231232')
else:
r = Vehicle(sys.argv[1], sys.argv[2], sys.argv[3]) # Username, Password, VIN

r = Vehicle(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) # Username, Password, VIN
r.requestUpdate() # Poll the car for an update
print(r.status()) # Print the status of the car

# r.unlock() # Unlock the doors

# time.sleep(10) # Wait 10 seconds

# r.lock() # Lock the doors
# r.lock() # Lock the doors
125 changes: 101 additions & 24 deletions fordpass/fordpass.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import requests
import logging
import time
import json
import os.path

defaultHeaders = {
'Accept': '*/*',
'Accept-Language': 'en-us',
'User-Agent': 'fordpass-na/353 CFNetwork/1121.2.2 Darwin/19.3.0',
'User-Agent': 'fordpass-ap/93 CFNetwork/1197 Darwin/20.0.0',
'Accept-Encoding': 'gzip, deflate, br',
}

apiHeaders = {
**defaultHeaders,
'Application-Id': '71A3AD0A-CF46-4CCF-B473-FC7FE5BC4592',
'Application-Id': '5C80A6BB-CF0D-4A30-BDBF-FC804B5C1A98',
'Content-Type': 'application/json',
}

baseUrl = 'https://usapi.cv.ford.com/api'

class Vehicle(object):
'''Represents a Ford vehicle, with methods for status and issuing commands'''
#Represents a Ford vehicle, with methods for status and issuing commands

def __init__(self, username, password, vin):
def __init__(self, username, password, vin, saveToken=False):
self.username = username
self.password = password
self.saveToken = saveToken
self.vin = vin
self.token = None
self.expires = None

def auth(self):
self.expiresAt = None
self.refresh_token = None
def auth(self):
'''Authenticate and store the token'''

data = {
Expand All @@ -41,32 +45,97 @@ def auth(self):
**defaultHeaders,
'Content-Type': 'application/x-www-form-urlencoded'
}
# Fetch OAUTH token stage 1
r = requests.post('https://sso.ci.ford.com/oidc/endpoint/default/token', data=data, headers=headers)

r = requests.post('https://fcis.ice.ibmcloud.com/v1.0/endpoint/default/token', data=data, headers=headers)
if r.status_code == 200:
logging.info('Succesfully fetched token Stage1')
result = r.json()
data = {
"code": result["access_token"]
}
headers = {
**apiHeaders
}
#Fetch OAUTH token stage 2 and refresh token
r = requests.put('https://api.mps.ford.com/api/oauth2/v1/token', data=json.dumps(data), headers=headers)
if r.status_code == 200:
result = r.json()
self.token = result['access_token']
self.refresh_token = result["refresh_token"]
self.expiresAt = time.time() + result['expires_in']
if self.saveToken:
result["expiry_date"] = time.time() + result['expires_in']
self.writeToken(result)
return True
else:
r.raise_for_status()


def refreshToken(self, token):
#Token is invalid so let's try refreshing it
data = {
"refresh_token": token["refresh_token"]
}
headers = {
**apiHeaders
}

r = requests.put('https://api.mps.ford.com/api/oauth2/v1/refresh', data=json.dumps(data), headers=headers)
if r.status_code == 200:
logging.info('Succesfully fetched token')
result = r.json()
if self.saveToken:
result["expiry_date"] = time.time() + result['expires_in']
self.writeToken(result)
self.token = result['access_token']
self.refresh_token = result["refresh_token"]
self.expiresAt = time.time() + result['expires_in']
return True
else:
r.raise_for_status()

def __acquireToken(self):
'''Fetch and refresh token as needed'''

if (self.token == None) or (time.time() >= self.expiresAt):
logging.info('No token, or has expired, requesting new token')
def __acquireToken(self):
#Fetch and refresh token as needed
#If file exists read in token file and check it's valid
if self.saveToken:
if os.path.isfile('/tmp/token.txt'):
data = self.readToken()
else:
data = dict()
data["access_token"] = self.token
data["refresh_token"] = self.refresh_token
data["expiry_date"] = self.expiresAt
else:
data = dict()
data["access_token"] = self.token
data["refresh_token"] = self.refresh_token
data["expiry_date"] = self.expiresAt
self.token=data["access_token"]
self.expiresAt = data["expiry_date"]
if self.expiresAt:
if time.time() >= self.expiresAt:
logging.info('No token, or has expired, requesting new token')
self.refreshToken(data)
#self.auth()
if self.token == None:
#No existing token exists so refreshing library
self.auth()
else:
logging.info('Token is valid, continuing')
pass


def writeToken(self, token):
#Save token to file to be reused
with open('/tmp/token.txt', 'w') as outfile:
token["expiry_date"] = time.time() + token['expires_in']
json.dump(token, outfile)

def readToken(self):
#Get saved token from file
with open('/tmp/token.txt') as token_file:
return json.load(token_file)

def status(self):
'''Get the status of the vehicle'''
#Get the status of the vehicle

self.__acquireToken()
self.__acquireToken()

params = {
'lrdt': '01-01-1970 00:00:00'
Expand All @@ -78,13 +147,14 @@ def status(self):
}

r = requests.get(f'{baseUrl}/vehicles/v4/{self.vin}/status', params=params, headers=headers)

if r.status_code == 200:
result = r.json()
if result["status"] == 402:
r.raise_for_status()
return result['vehiclestatus']
else:
r.raise_for_status()

def start(self):
'''
Issue a start command to the engine
Expand All @@ -109,7 +179,14 @@ def unlock(self):
'''
Issue an unlock command to the doors
'''
return self.__requestAndPoll('DELETE', f'{baseUrl}/vehicles/v2/{self.vin}/engine/start')
return self.__requestAndPoll('DELETE', f'{baseUrl}/vehicles/v2/{self.vin}/doors/lock')

def requestUpdate(self):
#Send request to refresh data from the cars module
self.__acquireToken()
status = self.__makeRequest('PUT', f'{baseUrl}/vehicles/v2/{self.vin}/status', None, None)
return status.json()["status"]


def __makeRequest(self, method, url, data, params):
'''
Expand All @@ -119,8 +196,8 @@ def __makeRequest(self, method, url, data, params):
headers = {
**apiHeaders,
'auth-token': self.token
}
}

return getattr(requests, method.lower())(url, headers=headers, data=data, params=params)

def __pollStatus(self, url, id):
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

setup(
name='fordpass',
version='0.0.3',
version='0.0.5',
author="Dave Clarke",
author_email="info@daveclarke.me",
description="Python wrapper for the FordPass API for Ford vehicle information and control: start, stop, lock, unlock.",
description="Python wrapper for the FordPass API for Ford vehicle information and control: start, stop, lock, unlock based upon the latest fordpass API",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/clarkd/fordpass-python",
Expand Down