Skip to content

Commit

Permalink
Updated demo apps and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
tdorssers committed Nov 28, 2021
1 parent cc45750 commit 7d00597
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 20 deletions.
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ The convenience method `api()` uses named endpoints listed in *endpoints.json* t
| Call | Description |
| --- | --- |
| `request()` | performs API call using relative or absolute URL, serialization and error message handling |
| `authorization_url()` | forms authorization URL with [PKCE](https://oauth.net/2/pkce/) extension |
| `fetch_token()` | requests a SSO token using Authorization Code grant with [PKCE](https://oauth.net/2/pkce/) extension |
| `refresh_token()` | requests a SSO token using [Refresh Token](https://oauth.net/2/grant-types/refresh-token/) grant |
| `logout()` | removes token from cache, returns logout URL and optionally signs out using system's default web browser |
| `vehicle_list()` | returns a list of Vehicle objects |
| `battery_list()` | returns a list of Battery objects |
| `solar_list()` | returns a list of SolarPanel objects |
Expand Down Expand Up @@ -152,6 +155,43 @@ with teslapy.Tesla('[email protected]', authenticator=custom_auth) as tesla:
tesla.fetch_token()
```

#### Alternative

TeslaPy 2.2.0 introduced the `authorization_url()` method to get the SSO page URL and supply the redirected URL as keyword argument `authorization_response` to `fetch_token()` after authentication.

```python
import teslapy
tesla = teslapy.Tesla('[email protected]')
if not tesla.authorized:
print('Use browser to login. Page Not Found will be shown at success.')
print('Open this URL: ' + tesla.authorization_url())
tesla.fetch_token(authorization_response=input('Enter URL after authentication: '))
vehicles = tesla.vehicle_list()
print(vehicles[0])
tesla.close()
```

#### Logout

To use your systems's default web browser to sign out of the SSO page and clear the token from cache:

```python
tesla.logout(sign_out=True)
```

If using pywebview, you can clear the token from cache and get the logout URL to display a sign out window:

```python
window = webview.create_window('Logout', tesla.logout())
window.start()
```

Selenium does not store cookies, just clear the token from cache:

```python
tesla.logout()
```

### Cache

The `Tesla` class implements a pluggable cache method. If you don't want to use the default disk caching, you can pass a function to load and return the cache dict, and a function that takes a dict as an argument to dump the cache dict, as arguments to the constructor. The `cache_loader` and `cache_dumper` arguments are accessible as attributes as well.
Expand Down Expand Up @@ -307,6 +347,7 @@ optional arguments:
-r, --stream receive streaming vehicle data on-change
-S, --service get service self scheduling eligibility
-V, --verify disable verify SSL certificate
-L, --logout clear token from cache and logout
--chrome use Chrome WebDriver
--edge use Edge WebDriver
--firefox use Firefox WebDriver
Expand Down Expand Up @@ -622,7 +663,7 @@ Make sure you have [Python](https://www.python.org/) 2.7+ or 3.5+ installed on y

`python -m pip install requests_oauthlib geopy pywebview selenium websocket-client`

and install [ChromeDriver](https://sites.google.com/chromium.org/driver/) to use Selenium or on Ubuntu 21.04 or newer as follows:
and install [ChromeDriver](https://sites.google.com/chromium.org/driver/) to use Selenium or on Ubuntu as follows:

`sudo apt-get install python3-requests-oauthlib python3-geopy python3-webview python3-selenium python3-websocket`

Expand Down
11 changes: 9 additions & 2 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

def custom_auth(url):
# Use pywebview if no web browser specified
if getattr(args, 'web', None) is None:
if webview and not (webdriver and args.web is not None):
result = ['']
window = webview.create_window('Login', url)
def on_loaded():
Expand All @@ -49,7 +49,6 @@ def main():
tesla.authenticator = custom_auth
if args.timeout:
tesla.timeout = args.timeout
tesla.fetch_token()
selected = prod = tesla.vehicle_list() + tesla.battery_list()
if args.filter:
selected = [p for p in prod for v in p.values() if v == args.filter]
Expand Down Expand Up @@ -91,6 +90,12 @@ def main():
print(product.api(args.api, **data))
else:
print(product.command(args.command, **data))
if args.logout:
if webview and not (webdriver and args.web is not None):
window = webview.create_window('Logout', tesla.logout())
webview.start()
else:
tesla.logout(not (webdriver and args.web is not None))

if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Tesla Owner API CLI')
Expand Down Expand Up @@ -129,6 +134,8 @@ def main():
help='get service self scheduling eligibility')
parser.add_argument('-V', '--verify', action='store_false',
help='disable verify SSL certificate')
parser.add_argument('-L', '--logout', action='store_true',
help='clear token from cache and logout')
if webdriver:
for c, s in enumerate(('chrome', 'edge', 'firefox', 'opera', 'safari')):
d, h = (0, ' (default)') if not webview and c == 0 else (None, '')
Expand Down
27 changes: 27 additions & 0 deletions gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ def __init__(self, **kwargs):
menu = Menu(self)
app_menu = Menu(menu, tearoff=0)
app_menu.add_command(label='Login', command=self.login)
app_menu.add_command(label='Logout', command=self.logout)
app_menu.add_separator()
app_menu.add_command(label='Exit', command=self.save_and_quit)
menu.add_cascade(label='App', menu=app_menu)
Expand Down Expand Up @@ -640,6 +641,32 @@ def process_login(self):
else:
self.status.text('No vehicles')

def logout(self):
""" Sign out and redraw dashboard """
if not hasattr(self, 'login_thread'):
return
# Use pywebview if available and selenium not selected
if webview and not self.selenium.get():
# Run in separate process
pool.apply(show_webview, (self.login_thread.tesla.logout(), ))
# Do not sign out if selenium is available and selected
self.login_thread.tesla.logout(not (webdriver and self.selenium.get()))
del self.vehicle
# Redraw dashboard
self.dashboard.pack_forget()
self.dashboard = Dashboard(self)
self.dashboard.pack(pady=5, fill=X)
# Remove vehicles from menu
self.vehicle_menu.delete(3, END)
# Disable commands
for i in range(0, 2):
self.vehicle_menu.entryconfig(i, state=DISABLED)
for i in range(0, self.cmd_menu.index(END) + 1):
self.cmd_menu.entryconfig(i, state=DISABLED)
for i in range(0, self.media_menu.index(END) + 1):
self.media_menu.entryconfig(i, state=DISABLED)
self.status.text('Not logged in')

def select(self):
""" Select vehicle and start new thread to get vehicle image """
self.vehicle = self.login_thread.vehicles[self.selected.get()]
Expand Down
33 changes: 16 additions & 17 deletions menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def show_vehicle_data(vehicle):
fmt = 'Is Front Defroster On: {:15} Is Rear Defroster On: {}'
print(fmt.format(str(cl['is_front_defroster_on']),
str(cl['is_rear_defroster_on'])))
print('-'*80)
print('-' * 80)
# Vehicle state
fmt = 'Vehicle Name: {:24} Odometer: {}'
print(fmt.format(ve['vehicle_name'], vehicle.dist_units(ve['odometer'])))
Expand Down Expand Up @@ -84,15 +84,15 @@ def show_vehicle_data(vehicle):
str(ve.get('sentry_mode'))))
fmt = 'Valet Mode: {:26} Valet Pin Set: {}'
print(fmt.format(str(ve['valet_mode']), str(not 'valet_pin_needed' in ve)))
print('-'*80)
print('-' * 80)
# Drive state
speed = 0 if dr['speed'] is None else dr['speed']
fmt = 'Power: {:31} Speed: {}'
print(fmt.format(str(dr['power']) + ' kW', vehicle.dist_units(speed, True)))
fmt = 'Shift State: {:25} Heading: {}'
print(fmt.format(str(dr['shift_state']), heading_to_str(dr['heading'])))
print(u'GPS: {:.75}'.format(location))
print('-'*80)
print('-' * 80)
# Charging state
fmt = 'Charging State: {:22} Time To Full Charge: {:02.0f}:{:02.0f}'
print(fmt.format(ch['charging_state'],
Expand All @@ -116,7 +116,7 @@ def show_vehicle_data(vehicle):
fmt = 'Charge Port Door Open: {:15} Charge Port Latch: {}'
print(fmt.format(str(ch['charge_port_door_open']),
str(ch['charge_port_latch'])))
print('-'*80)
print('-' * 80)
# Vehicle config
fmt = 'Car Type: {:28} Exterior Color: {}'
print(fmt.format(co['car_type'], co['exterior_color']))
Expand All @@ -132,7 +132,7 @@ def show_charging_sites(vehicle):
for site in sites['destination_charging']:
print(fmt.format(site['name'],
vehicle.dist_units(site['distance_miles'])))
print('-'*80)
print('-' * 80)
print('Superchargers:')
fmt = '{:57} {} {}/{} stalls'
for site in sites['superchargers']:
Expand All @@ -154,20 +154,20 @@ def menu(vehicle):
if vehicle['state'] == 'online':
if not vehicle.mobile_enabled():
print('Mobile access is not enabled for this vehicle')
print('-'*80)
print('-' * 80)
show_vehicle_data(vehicle.get_vehicle_data())
else:
print('Wake up vehicle to use remote functions/telemetry')
print('-'*80)
print('-' * 80)
# Display 3 column menu
for i, option in enumerate(lst, 1):
print('{:2} {:23}'.format(i, option), end='' if i % 3 else '\n')
if i % 3:
print()
print('-'*80)
print('-' * 80)
# Get user choice
opt = int(raw_input("Choice (0 to quit): "))
print('-'*80)
print('-' * 80)
# Check if vehicle is still online, otherwise force refresh
if opt > 2:
vehicle.get_vehicle_summary()
Expand All @@ -181,7 +181,7 @@ def menu(vehicle):
elif opt == 2:
print('Please wait...')
vehicle.sync_wake_up()
print('-'*80)
print('-' * 80)
elif opt == 3:
show_charging_sites(vehicle)
elif opt == 4:
Expand Down Expand Up @@ -242,7 +242,7 @@ def menu(vehicle):

def custom_auth(url):
# Use pywebview if no web browser specified
if getattr(args, 'web', None) is None:
if webview and not (webdriver and args.web is not None):
result = ['']
window = webview.create_window('Login', url)
def on_loaded():
Expand Down Expand Up @@ -272,24 +272,23 @@ def main():
geopy.geocoders.options.default_ssl_context = ctx
email = raw_input('Enter email: ')
with Tesla(email, verify=args.verify, proxy=args.proxy) as tesla:
if webdriver:
if (webdriver and args.web is not None) or webview:
tesla.authenticator = custom_auth
if args.timeout:
tesla.timeout = args.timeout
tesla.fetch_token()
vehicles = tesla.vehicle_list()
print('-'*80)
print('-' * 80)
fmt = '{:2} {:25} {:25} {:25}'
print(fmt.format('ID', 'Display name', 'VIN', 'State'))
for i, vehicle in enumerate(vehicles):
print(fmt.format(i, vehicle['display_name'], vehicle['vin'],
vehicle['state']))
print('-'*80)
print('-' * 80)
idx = int(raw_input("Select vehicle: "))
print('-'*80)
print('-' * 80)
print('VIN decode:', ', '.join(vehicles[idx].decode_vin().values()))
print('Option codes:', ', '.join(vehicles[idx].option_code_list()))
print('-'*80)
print('-' * 80)
menu(vehicles[idx])

if __name__ == "__main__":
Expand Down

0 comments on commit 7d00597

Please sign in to comment.