Control Tesla Cars
Test setting temp based on local weather.
driver_temp=
passenger_temp=
This is a delivery system. replace buses in cities with electrical cars. These cars run the same route as buses.
Going to change the radio
More indepth Tesla car stats. Returns the Raspberry Pi media system onboard that is used to stream media to the car. Pluhs in to the cars stero. Allows passangers in the bar to reclievve calls. Broadcast wifi. Each car has its own router that it uses to comm with the outside world. Canera t oo report how everything is goiing. If things are wrong realtime images of what is happening. The ability to for remote login and control. HAM Radio broadcast. Each car is broadcasting a media center that offers storage space and media. You are able to connect to any of these Tesla cars and access the services they offer. If for some reason they are not to total standards then you can retry.
Nux the
Switch between low and high heat.
passanger temp
config file with times to auto start the car. driverless commands. sync to meetup and drive to where the meetups are happening
{ "response": { "inside_temp": 17.0, // degC inside car "outside_temp": 9.5, // degC outside car or null "driver_temp_setting": 22.6, // degC of driver temperature setpoint "passenger_temp_setting": 22.6, // degC of passenger temperature setpoint "is_auto_conditioning_on": false, // apparently even if on "is_front_defroster_on": null, // null or boolean as integer? "is_rear_defroster_on": false, "fan_status": 0 // fan speed 0-6 or null } }
In [2]:
import json
#import nose
from cryptography.fernet import Fernet
import getpass
import pandas
In [ ]:
myusr = getpass.getuser()
In [7]:
tespas = getpass.getpass('PASSWORD ')
In [9]:
mydict = ({ "response": { "df": False, "dr": False, "pf": False, "pr": False, "ft": False, "rt": False, "car_verson": "1.19.42", "locked": True, "sun_roof_installed": False, "sun_roof_state": "unknown", "sun_roof_percent_open": 0, "dark_rims": False, "wheel_type": "Base19", "has_spoiler": False, "roof_color": "Colored", "perf_config": "Base" } })
In [10]:
tespas
Out[10]:
In [11]:
pandas.DataFrame(mydict)
Out[11]:
In [16]:
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b'straz')
print(token)
'...'
f.decrypt(token)
Out[16]:
In [17]:
key
Out[17]:
In [18]:
tesveh = 'https://owner-api.teslamotors.com/api/1/vehicles/1/command/remote_start_drive?password='
In [19]:
tesveh
Out[19]:
In [21]:
#from urllib2 import Request, urlopen
#headers = {
# 'Authorization': 'Bearer {access_token}'
#}
#request = Request('https://owner-api.teslamotors.com/api/1/vehicles/1/command/remote_start_drive?password=edisonsux', headers=headers)
#response_body = urlopen(request).read()
#print response_body
In [22]:
doorquen = (mydict['response']['df'], mydict['response']['dr'], mydict['response']['pf'], mydict['response']['pr'])
In [23]:
doorquen
Out[23]:
In [24]:
key
Out[24]:
In [26]:
keyencr = f.encrypt(key)
In [27]:
keyencr
Out[27]:
In [28]:
f.decrypt(token)
Out[28]:
In [29]:
TESLACONFIG = ('TESLA CONTROL BRUM BRUM')
In [30]:
print(TESLACONFIG)
In [ ]:
In [31]:
passwrdz = getpass.getpass("PASSWORD ENTER ")
askmileorkilo = input('AMERICAN SETTINGS Y/n ')
setlowtemp = input('DRIVER LOW TEMP SET AS: ')
sethightemp = input('DRIVER HIGH TEMP SET AS: ')
setpasslow = input('PASS LOW TEMP SET AS: ')
setpashight = input('PASS HIGH TEMP SET AS: ')
openDriversDoor = input('OPEN DRIVERS DOOR Y/n ')
unlockcar = input('UNLOCK CAR Y/n ')
valetmode = input('VALET MODE Y/n ')
valetpass = getpass.getpass('4 PIN NUMBER: ')
In [32]:
passwrdz
Out[32]:
In [33]:
valint = int(valetpass)
In [34]:
SETCURRENTZ = input('Current from -1 to 1: ')
In [35]:
flsecur = float(SETCURRENTZ)
In [36]:
flsecur
Out[36]:
In [37]:
int(SETCURRENTZ)
Out[37]:
In [39]:
import cryptography
In [183]:
#fercypo = cryptography.fernet()
{ "portal_url": "https://owner-api.teslamotors.com/api/1/vehicles/", "stream_url": "https://streaming.vn.teslamotors.com/stream/", "username": "yourMyTeslaLogin@email.com", "password": "yourPassword", "output_file": "stream_output.txt" }
In [41]:
f.generate_key()
Out[41]:
In [43]:
token = f.encrypt(bytes(valint))
In [44]:
print(token)
In [45]:
f.decrypt(token)
Out[45]:
In [46]:
int.from_bytes((f.decrypt(token)), byteorder='big')
Out[46]:
In [47]:
byedecrpt = (f.decrypt(token))
In [48]:
int.from_bytes((byedecrpt), byteorder='big')
Out[48]:
In [50]:
if 'n' in valetmode:
valeton = False
elif 'Y' in valetmode:
valeton = True
In [52]:
if 'n' in askmileorkilo:
miletru = False
elif 'Y' in askmileorkilo:
miletru = True
In [53]:
askmileorkilo
Out[53]:
In [54]:
miletru
Out[54]:
In [55]:
if miletru == True:
guidisuni = "mi/hr"
elif miletru == False:
guidisuni = "km/hr"
In [56]:
guidisuni
Out[56]:
In [57]:
if 'n' in unlockcar:
opddor = False
elif 'Y' in unlockcar:
opddor = True
In [58]:
opddor
Out[58]:
In [59]:
if 'n' in openDriversDoor:
opddor = False
elif 'Y' in openDriversDoor:
opddor = True
In [60]:
opddor
Out[60]:
In [61]:
import configparser
In [62]:
dfstat = (mydict['response']['df'])
In [63]:
pfstat = (mydict['response']['pf'])
In [64]:
prstat = (mydict['response']['pr'])
In [65]:
drstat = (mydict['response']['dr'])
In [66]:
drstat
Out[66]:
In [67]:
prstat
Out[67]:
In [68]:
dfstat
Out[68]:
In [69]:
pfstat
Out[69]:
In [70]:
import arrow
In [71]:
timnow = arrow.now()
In [72]:
print(timnow.datetime)
In [73]:
timnow.for_json()
Out[73]:
In [74]:
timnow.isoweekday()
Out[74]:
In [75]:
timnow.humanize()
Out[75]:
In [76]:
timnow.isoweekday()
Out[76]:
In [77]:
timnow.isocalendar()
Out[77]:
In [78]:
timnow.ceil
Out[78]:
In [79]:
timnow.clone()
Out[79]:
In [80]:
timnow.date()
Out[80]:
In [81]:
timnow.format()
Out[81]:
In [82]:
timnow.floor
Out[82]:
In [83]:
timnow.float_timestamp
Out[83]:
In [84]:
timz = timnow.for_json()
In [85]:
timz
Out[85]:
In [86]:
print(timz)
In [87]:
timz.upper()
Out[87]:
In [88]:
timz.split('-' and ':' and '.')
Out[88]:
In [89]:
print(timnow)
In [90]:
timutc = timnow.utcnow()
In [ ]:
In [91]:
timnow.weekday()
Out[91]:
In [92]:
timnow.dst()
Out[92]:
In [93]:
timnow.ctime()
Out[93]:
In [95]:
arrow.util.total_seconds
Out[95]:
In [96]:
arrow.api.factory
Out[96]:
In [97]:
artz = arrow.factory.tzinfo(timutc)
In [99]:
calcsee= arrow.locales.calendar.HTMLCalendar()
In [100]:
calcsee.formatmonth(2016, 5)
Out[100]:
In [101]:
arnow = arrow.now()
In [102]:
dayweekz = arnow.weekday()
In [103]:
dayweekz
Out[103]:
In [104]:
calcsee.getfirstweekday()
Out[104]:
In [105]:
calcsee.getfirstweekday()
Out[105]:
In [109]:
arnow.strftime('%M')
Out[109]:
In [110]:
arnow.strftime('%m')
Out[110]:
In [111]:
arnow.strftime('%Y %m')
Out[111]:
In [ ]:
In [112]:
yrints = int(arnow.strftime('%Y'))
In [113]:
yrints
Out[113]:
In [114]:
mondayfor = int(arnow.strftime('%m'))
In [115]:
mondayfor
Out[115]:
In [116]:
daydatye = int(arnow.strftime('%d'))
In [117]:
daydatye
Out[117]:
In [118]:
calcsee.formatday(2016, 1)
Out[118]:
In [120]:
panread = pandas.read_html(calcsee.formatmonth(yrints, mondayfor))
In [121]:
panread[0]
Out[121]:
In [122]:
calcsee.formatmonth(yrints, mondayfor)
Out[122]:
In [125]:
import os
In [126]:
import random
In [ ]:
In [127]:
calcday = calcsee.cssclasses
In [128]:
calcday
Out[128]:
In [129]:
calcfulday = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
In [130]:
len(calcfulday)
Out[130]:
In [ ]:
In [131]:
random.choice(calcfulday)
Out[131]:
In [132]:
dayweekz
Out[132]:
In [133]:
calcfulday[dayweekz]
Out[133]:
In [ ]:
In [134]:
print(calcday[dayweekz])
In [136]:
random.choice(calcday)
Out[136]:
In [ ]:
In [137]:
calcsee.firstweekday
Out[137]:
In [138]:
calcsee.formatday
Out[138]:
In [139]:
calcsee.formatmonth
Out[139]:
In [140]:
calcsee.
In [141]:
passwrdz
Out[141]:
In [142]:
f.encrypt
Out[142]:
In [143]:
token = f.encrypt(passwrdz)
In [144]:
import hashlib
In [145]:
print(hashlib.algorithms_available)
print(hashlib.algorithms_guaranteed)
In [146]:
#mystring = input('Enter String to hash: ')
# Assumes the default UTF-8
hash_object = hashlib.sha512(passwrdz.encode())
print(hash_object.hexdigest())
In [147]:
print(hash_object.name)
In [148]:
print(hash_object.block_size)
In [149]:
import subprocess
In [ ]:
tempchec = subprocess.check_output(["/opt/vc/bin/vcgencmd", "measure_temp"])
In [ ]:
print(float(tempchec.split(‘=’)[1][:-3]))
In [ ]:
tempchec
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
subprocess.check_output(o)
In [ ]:
s = subprocess.check_output([“/opt/vc/bin/vcgencmd”,”measure_temp”])
print(float(s.split(‘=’)[1][:-3]))
In [152]:
import socket
socket.gethostbyname(socket.gethostname())
Out[152]:
In [153]:
socket.gethostname()
Out[153]:
In [154]:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 0)) # connecting to a UDP address doesn't send packets
local_ip_address = s.getsockname()[0]
In [155]:
local_ip_address
Out[155]:
In [156]:
s.getsockname()
Out[156]:
In [ ]:
In [157]:
socket.gethostbyaddr(socket.gethostname())
Out[157]:
In [160]:
socket.getaddrinfo
Out[160]:
In [161]:
subup = subprocess.check_output(['uptime'])
In [162]:
loaspli = subup.split('load average: ')
In [ ]:
In [163]:
'''
import os
import subprocess
BAT_PATH = "/proc/acpi/battery/BAT%d"
def get_full_charge(batt_path):
"""Get the max capacity of the battery
:param batt_path: The dir path to the battery (acpi) processes
:type batt_path: string
:returns: The max capacity of the battery
:rtype: int
"""
p1 = subprocess.Popen(["grep",
"last full capacity",
batt_path + "/info"],
stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk",
"{print $4}"],
stdin=p1.stdout,
stdout=subprocess.PIPE)
p1.stdout.close()
return int(p2.communicate()[0])
def get_current_charge(batt_path):
"""Get the current capacity of the battery
:param batt_path: The dir path to the battery (acpi) processes
:type batt_path: string
:returns: The current capacity of the battery
:rtype: int
"""
p1 = subprocess.Popen(["grep",
"remaining capacity",
batt_path + "/state"],
stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk",
"{print $3}"],
stdin=p1.stdout,
stdout=subprocess.PIPE)
p1.stdout.close()
return int(p2.communicate()[0])
def guess_battery_path():
"""Gets the path of the battery (BAT0, BAT1...)
:returns: The path to the battery acpi process information
:rtype: string
"""
i = 0
while True:
if os.path.exists(BAT_PATH % i):
return BAT_PATH % i
i += 1
def is_plugged(batt_path):
"""Returns a flag saying if the battery is plugged in or not
:param batt_path: The dir path to the battery (acpi) processes
:type batt_path: string
:returns: A flag, true is plugged, false unplugged
:rtype: bool
"""
p = subprocess.Popen(["grep",
"charging state",
batt_path + "/state"],
stdout=subprocess.PIPE)
return "discharging" not in p.communicate()[0]
def get_battery_percent(batt_path):
"""Calculates the percent of the battery based on the different data of
the battery processes
:param batt_path: The dir path to the battery (acpi) processes
:type batt_path: string
:returns: The percent translation of the battery total and current capacity
:rtype: int
"""
return get_current_charge(batt_path) * 100 / get_full_charge(batt_path)
def main():
path = guess_battery_path()
print("Current battery percent: %d" % get_battery_percent(path))
print("Plugged in" if is_plugged(path) else "Not plugged in")
if __name__ == "__main__":
main()
'''
Out[163]:
In [ ]:
s = subprocess.check_output([“uptime”])
load_split = s.split(‘load average: ‘)
load_five = float(load_split[1].split(‘,’)[1])
up = load_split[0]
up_pos = up.rfind(‘,’,0,len(up)-4)
up = up[:up_pos].split(‘up ‘)[1]
return ( up , load_five )
In [165]:
'''
import subprocess
import os
def get_ram():
“Returns a tuple (total ram, available ram) in megabytes. See www.linuxatemyram.com”
try:
s = subprocess.check_output([“free”,”-m”])
lines = s.split(‘\n’)
return ( int(lines[1].split()[1]), int(lines[2].split()[3]) )
except:
return 0def get_process_count():
“Returns the number of processes”
try:
s = subprocess.check_output([“ps”,”-e”])
return len(s.split(‘\n’))
except:
return 0def get_up_stats():
“Returns a tuple (uptime, 5 min load average)”
try:
s = subprocess.check_output([“uptime”])
load_split = s.split(‘load average: ‘)
load_five = float(load_split[1].split(‘,’)[1])
up = load_split[0]
up_pos = up.rfind(‘,’,0,len(up)-4)
up = up[:up_pos].split(‘up ‘)[1]
return ( up , load_five )
except:
return ( ” , 0 )def get_connections():
“Returns the number of network connections”
try:
s = subprocess.check_output([“netstat”,”-tun”])
return len([x for x in s.split() if x == ‘ESTABLISHED’])
except:
return 0def get_temperature():
“Returns the temperature in degrees C”
try:
s = subprocess.check_output([“/opt/vc/bin/vcgencmd”,”measure_temp”])
return float(s.split(‘=’)[1][:-3])
except:
return 0def get_ipaddress():
“Returns the current IP address”
arg=’ip route list’
p=subprocess.Popen(arg,shell=True,stdout=subprocess.PIPE)
data = p.communicate()
split_data = data[0].split()
ipaddr = split_data[split_data.index(‘src’)+1]
return ipaddr
def get_cpu_speed():
“Returns the current CPU speed”
f = os.popen(‘/opt/vc/bin/vcgencmd get_config arm_freq’)
cpu = f.read()
return cpu
print ‘Free RAM: ‘+str(get_ram()[1])+’ (‘+str(get_ram()[0])+’)’
print ‘Nr. of processes: ‘+str(get_process_count())
print ‘Up time: ‘+get_up_stats()[0]
print ‘Nr. of connections: ‘+str(get_connections())
print ‘Temperature in C: ‘ +str(get_temperature())
print ‘IP-address: ‘+get_ipaddress()
print ‘CPU speed: ‘+str(get_cpu_speed())
'''
Out[165]:
In [ ]:
#import ConfigParser
config = configparser.RawConfigParser()
# When adding sections or items, add them in the reverse order of
# how you want them to be displayed in the actual file.
# In addition, please note that using RawConfigParser's and the raw
# mode of ConfigParser's respective set functions, you can assign
# non-string values to keys internally, but will receive an error
# when attempting to write to a file or when you get it in non-raw
# mode. SafeConfigParser does not allow such assignments to take place.
config.add_section('userinfo')
config.set('userinfo', 'username', myusr)
config.set('userinfo', 'password', (hash_object.hexdigest()))
config.add_section('tempsetting')
config.set('tempsetting', 'drivertemphigh', sethightemp)
config.set('tempsetting', 'drivertemplow', setlowtemp)
config.set('tempsetting', 'passtemphigh', sethightemp)
config.set('tempsetting', 'passtemplow', setlowtemp)
config.add_section('dooropenstatus')
#config.set('doorstatus', 'drifronop', )
config.set('dooropenstatus', 'driversfront', dfstat)
config.set('dooropenstatus', 'passfront', pfstat)
config.set('dooropenstatus', 'passrear', prstat)
config.set('dooropenstatus', 'driversrear', drstat)
config.add_section('doorlockstatus')
config.set('doorlockstatus', 'driversfont', opddor)
config.set('doorlockstatus', 'passfront', opddor)
config.set('doorlockstatus', 'passrear', opddor)
config.set('doorlockstatus', 'driverrear', opddor)
config.add_section('chargestatus')
config.set('chargestatus', 'charging', 'complete')
config.set('chargestatus', "battcurrent", flsecur)
config.set('chargestatus', 'chargvolt', 2.5)
config.set('chargestatus', 'timecharge', 60)
#config.set('charges')
#Set Valet Mode
config.add_section('valetmode')
config.set('valetmode', 'valeton', valeton)
config.set('valetmode', 'timenow', (timnow))
config.set('valetmode', 'traveltime', 40)
config.set('valetmode', 'dayname', calcfulday[dayweekz])
config.set('valetmode', 'timehuman', timnow.humanize())
#config.set('templow', 'an_int', setlowtemp)
#config.set('drivefront', 'an_int', '3')
#config.set('passfront', 'baz', 'fun')
#config.set('passback', 'bar', 'Python')
#config.set('driveback', 'foo', '%(bar)s is %(baz)s!')
# Writing our configuration file to 'example.cfg'
with open('tesla.cfg', 'w') as configfile:
config.write(configfile)
In [ ]:
crea = config.read
In [ ]:
with open('tesla.cfg', 'r') as configfile:
print(config.read(configfile))
In [ ]:
temphi = 32
templo = 17
tempstr = ("{'temphi' : " + str(temphi) + '}')
In [ ]:
templow = ("{'templow' : " + str(templo) + '}')
In [ ]:
templow
{ "response": { "gui_distance_units": "mi/hr", "gui_temperature_units": "F", "gui_charge_rate_units": "mi/hr", "gui_24_hour_time": false, "gui_range_display": "Rated" } }tempstr
{ "response": { "charging_state": "Complete", // "Charging", ?? "charge_to_max_range": false, // current std/max-range setting "max_range_charge_counter": 0, "fast_charger_present": false, // connected to Supercharger? "battery_range": 239.02, // rated miles "est_battery_range": 155.79, // range estimated from recent driving "ideal_battery_range": 275.09, // ideal miles "battery_level": 91, // integer charge percentage "battery_current": -0.6, // current flowing into battery "charge_starting_range": null, "charge_starting_soc": null, "charger_voltage": 0, // only has value while charging "charger_pilot_current": 40, // max current allowed by charger & adapter "charger_actual_current": 0, // current actually being drawn "charger_power": 0, // kW (rounded down) of charger "time_to_full_charge": null, // valid only while charging "charge_rate": -1.0, // float mi/hr charging or -1 if not charging "charge_port_door_open": true } }
Returns the current temperature and climate control state.
var TEMP_HI = 32; var TEMP_LO = 17; function set_temperature( params, cb ) { var dtemp = params.dtemp; var ptemp = params.ptemp; var vid = params.id; var error = false;
//var temp_str = "";
if ( dtemp !== undefined && dtemp <= TEMP_HI && dtemp >= TEMP_LO) {
//temp_str = 'driver_temp=' + dtemp; // change from string to JSON form data
} else {
error = true;
}
// if no passenger temp is passed, the driver temp is also used as the passenger temp
if ( ptemp !== undefined && ptemp <= TEMP_HI && ptemp >= TEMP_LO) {
//temp_str = temp_str +'&passenger_temp=' + ptemp; // change from string to JSON form data
} else if ( ptemp === undefined ) {
ptemp = dtemp;
} else {
error = true;
}
if (!error) {
request( {
method: 'POST',
url: portal + '/vehicles/' + vid + '/command/set_temps',
gzip: true,
headers: http_header,
form: {
"driver_temp" : dtemp.toString(),
"passenger_temp" : ptemp.toString(),
}
}, function (error, response, body) {
if ((!!error) || (response.statusCode !== 200)) return report(error, response, body, cb);
try {
var data = JSON.parse(body);
if (typeof cb == 'function') return cb( data.response );
else return true;
} catch (err) {
return report2('set_temps', body, cb);
}
});
} else {
if (typeof cb == 'function') return cb( new Error('Invalid temperature setting (' + dtemp + 'C), Passenger (' + ptemp + 'C)'));
else return false;
}
} exports.set_temperature = set_temperature; exports.TEMP_HI = TEMP_HI; exports.TEMP_LO = TEMP_LO;
{ "response": { "df": false, // driver's side front door open "dr": false, // driver's side rear door open "pf": false, // passenger's side front door open "pr": false, // passenger's side rear door open "ft": false, // front trunk is open "rt": false, // rear trunk is open "car_verson": "1.19.42", // car firmware version "locked": true, // car is locked "sun_roof_installed": false, // panoramic roof is installed "sun_roof_state": "unknown", "sun_roof_percent_open": 0, // null if not installed "dark_rims": false, // gray rims installed "wheel_type": "Base19", // wheel type installed "has_spoiler": false, // spoiler is installed "roof_color": "Colored", // "None" for panoramic roof "perf_config": "Base" } }
{ "response": { "charging_state": "Complete", // "Charging", ?? "charge_to_max_range": false, // current std/max-range setting "max_range_charge_counter": 0, "fast_charger_present": false, // connected to Supercharger? "battery_range": 239.02, // rated miles "est_battery_range": 155.79, // range estimated from recent driving "ideal_battery_range": 275.09, // ideal miles "battery_level": 91, // integer charge percentage "battery_current": -0.6, // current flowing into battery "charge_starting_range": null, "charge_starting_soc": null, "charger_voltage": 0, // only has value while charging "charger_pilot_current": 40, // max current allowed by charger & adapter "charger_actual_current": 0, // current actually being drawn "charger_power": 0, // kW (rounded down) of charger "time_to_full_charge": null, // valid only while charging "charge_rate": -1.0, // float mi/hr charging or -1 if not charging "charge_port_door_open": true } }
More car info
What music is being played.
Is my car unlocked? Unlock when device within certain distance.
Checks that all the doors are shut
https://owner-api.teslamotors.com/api/1/vehicles/vehicle_id/command/door_unlock
https://owner-api.teslamotors.com/api/1/vehicles/vehicle_id/command/door_lock
In [166]:
for doorq in doorquen:
if doorq == True:
print('Warning Door Open')
In [167]:
for doorq in doorquen:
#print(doorq)
if doorq == False:
#for inz in range():
print('Warning Door Close')
In [ ]:
In [168]:
mydict['response']['dr']
Out[168]:
In [169]:
mydict['response']['pf']
Out[169]:
In [170]:
mydict['response']['pr']
Out[170]:
In [172]:
def testdoorlock():
assert mydict['response']['locked'] == 'True'
def testdoorunlock():
assert mydict['response']['locked'] == 'False'
In [ ]:
In [173]:
mydict['response']['locked']
Out[173]:
In [174]:
mydict['response']['df']
Out[174]:
In [ ]:
In [175]:
mydict['response']['dr']
Out[175]:
In [176]:
mydict['response']['pf']
Out[176]:
In [177]:
mydict['response']['pr']
Out[177]:
In [178]:
vechj = open('/home/wcm/git/vech.json', 'r')
In [179]:
rdvechj = vechj.read()
In [180]:
rdvechj
Out[180]:
In [181]:
json.loads(rdvechj)
Out[181]: