def main(): parser = ArgumentParser() parser.add_argument('username', help='Tesla username, usually an email address') parser.add_argument('--output', type=FileType('w'), default='-') parser.add_argument('--start', type=parse_date) parser.add_argument('--end', type=parse_date) args = parser.parse_args() password = os.environ.get('TESLA_PASSWORD') if not password: password = getpass(f'Password for {args.username}:') tesla = Tesla(args.username, password) writer = DictWriter( args.output, fieldnames=['battery_id', 'serial_number', 'timestamp', 'solar_power']) writer.writeheader() for battery in tesla.battery_list(): battery_id = battery['id'] for end_date in tesla_end_dates(args.start, args.end): data = battery.get_calendar_history_data(kind='power', end_date=end_date) assert data["installation_time_zone"] == "Europe/London" serial_number = data['serial_number'] for row in data['time_series']: writer.writerow( dict(battery_id=battery_id, serial_number=serial_number, timestamp=row['timestamp'], solar_power=row['solar_power']))
def main(): default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, format=default_format) if not args.verify: # Disable SSL verify for Nominatim ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE 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 and args.web is not None) or webview: tesla.authenticator = custom_auth if args.timeout: tesla.timeout = args.timeout vehicles = tesla.vehicle_list() 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) idx = int(raw_input("Select vehicle: ")) print('-' * 80) print('VIN decode:', ', '.join(vehicles[idx].decode_vin().values())) print('Option codes:', ', '.join(vehicles[idx].option_code_list())) print('-' * 80) menu(vehicles[idx])
def main(): default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.DEBUG, format=default_format) email = raw_input('Enter email: ') password = getpass.getpass('Password: '******'-' * 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) idx = int(raw_input("Select vehicle: ")) print('-' * 80) print('VIN decode:', ', '.join(vehicles[idx].decode_vin().values())) print('Option codes:', ', '.join(vehicles[idx].option_code_list())) print('-' * 80) menu(vehicles[idx])
def main(): parser = argparse.ArgumentParser(description='Tesla Owner API CLI') parser.add_argument('-e', dest='email', help='login email', required=True) parser.add_argument('-p', dest='password', nargs='?', const='', help='prompt/specify login password') parser.add_argument('-t', dest='passcode', help='two factor passcode') parser.add_argument('-u', dest='factor', help='use two factor device name') parser.add_argument('-f', dest='filter', help='filter on id, vin, etc.') parser.add_argument('-a', dest='api', help='API call endpoint name') parser.add_argument('-k', dest='keyvalue', help='API parameter (key=value)', action='append', type=lambda kv: kv.split('=', 1)) parser.add_argument('-c', dest='command', help='vehicle command endpoint') parser.add_argument('-l', '--list', action='store_true', help='list all selected vehicles') parser.add_argument('-o', '--option', action='store_true', help='list vehicle option codes') parser.add_argument('-v', '--vin', action='store_true', help='vehicle identification number decode') parser.add_argument('-w', '--wake', action='store_true', help='wake up selected vehicle(s)') parser.add_argument('-g', '--get', action='store_true', help='get rollup of all vehicle data') parser.add_argument('-n', '--nearby', action='store_true', help='list nearby charging sites') parser.add_argument('-m', '--mobile', action='store_true', help='get mobile enabled state') parser.add_argument('-s', '--start', action='store_true', help='remote start drive') parser.add_argument('-d', '--debug', action='store_true', help='set logging level to debug') args = parser.parse_args() default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, format=default_format) if args.password == '': password = getpass.getpass('Password: '******'%d vehicle(s), %d selected', len(cars), len(selected)) for i, vehicle in enumerate(selected): print('Vehicle %d:' % i) if args.list: print(vehicle) if args.option: print(', '.join(vehicle.option_code_list())) if args.vin: print(vehicle.decode_vin()) if args.wake: vehicle.sync_wake_up() if args.get: print(vehicle.get_vehicle_data()) if args.nearby: print(vehicle.get_nearby_charging_sites()) if args.mobile: print(vehicle.mobile_enabled()) if args.start: print(vehicle.remote_start_drive()) if args.api: data = dict(args.keyvalue) if args.keyvalue else {} print(vehicle.api(args.api, **data))
def main(): default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, format=default_format) with Tesla(args.email, verify=args.verify, proxy=args.proxy, sso_base_url=args.url) as tesla: if (webdriver and args.web is not None) or webview: tesla.authenticator = custom_auth if args.timeout: tesla.timeout = args.timeout if args.refresh: tesla.refresh_token(refresh_token=args.refresh) 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 ] logging.info('%d product(s), %d selected', len(prod), len(selected)) for i, product in enumerate(selected): print('Product %d:' % i) # Show information or invoke API depending on arguments if args.list: print(product) if isinstance(product, Vehicle): if args.option: print(', '.join(product.option_code_list())) if args.vin: print(product.decode_vin()) if args.wake: product.sync_wake_up() if args.get: print(product.get_vehicle_data()) if args.nearby: print(product.get_nearby_charging_sites()) if args.mobile: print(product.mobile_enabled()) if args.stream: product.stream(print) if args.service: print(product.get_service_scheduling_data()) if args.history: print(product.get_charge_history()) elif isinstance(product, Battery) and args.battery: print(product.get_battery_data()) elif isinstance(product, SolarPanel) and args.site: print(product.get_site_data()) if args.api or args.command: data = {} for key, value in args.keyvalue or []: try: data[key] = ast.literal_eval(value) except (SyntaxError, ValueError): data[key] = value if args.api: for commandargs in args.api: command = commandargs.pop(0) command_data = {} for keyvalue in commandargs or []: key, value = keyvalue.split('=', 1) try: command_data[key] = ast.literal_eval(value) except (SyntaxError, ValueError): command_data[key] = value if not command_data: command_data = data print(product.api(command, **command_data)) else: print(product.command(args.command, **data)) if args.user: print(product.get_user_details()) 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))
def main(): parser = argparse.ArgumentParser(description='Tesla Owner API CLI') parser.add_argument('-e', dest='email', help='login email', required=True) parser.add_argument('-p', dest='password', nargs='?', const='', help='prompt/specify login password') parser.add_argument('-t', dest='passcode', help='two factor passcode') parser.add_argument('-u', dest='factor', help='use two factor device name') parser.add_argument('-f', dest='filter', help='filter on id, vin, etc.') parser.add_argument('-a', dest='api', help='API call endpoint name') parser.add_argument('-k', dest='keyvalue', help='API parameter (key=value)', action='append', type=lambda kv: kv.split('=', 1)) parser.add_argument('-c', dest='command', help='product command endpoint') parser.add_argument('-l', '--list', action='store_true', help='list all selected vehicles/batteries') parser.add_argument('-o', '--option', action='store_true', help='list vehicle option codes') parser.add_argument('-v', '--vin', action='store_true', help='vehicle identification number decode') parser.add_argument('-w', '--wake', action='store_true', help='wake up selected vehicle(s)') parser.add_argument('-g', '--get', action='store_true', help='get rollup of all vehicle data') parser.add_argument('-b', '--battery', action='store_true', help='get detailed battery state and config') parser.add_argument('-n', '--nearby', action='store_true', help='list nearby charging sites') parser.add_argument('-m', '--mobile', action='store_true', help='get mobile enabled state') parser.add_argument('-s', '--start', action='store_true', help='remote start drive') parser.add_argument('-d', '--debug', action='store_true', help='set logging level to debug') args = parser.parse_args() default_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, format=default_format) if args.password == '': password = getpass.getpass('Password: '******'%d product(s), %d selected', len(prod), len(selected)) for i, product in enumerate(selected): print('Product %d:' % i) # Show information or invoke API depending on arguments if args.list: print(product) if isinstance(product, Vehicle): if args.option: print(', '.join(product.option_code_list())) if args.vin: print(product.decode_vin()) if args.wake: product.sync_wake_up() if args.get: print(product.get_vehicle_data()) if args.nearby: print(product.get_nearby_charging_sites()) if args.mobile: print(product.mobile_enabled()) if args.start: print(product.remote_start_drive()) elif args.battery: print(product.get_battery_data()) if args.api: data = {} for key, value in args.keyvalue or []: try: data[key] = ast.literal_eval(value) except ValueError: data[key] = value print(product.api(args.api, **data))
def __tesla_connect(self, email): """ Connect to vehicles associated with email address """ self.tesla = Tesla(email) self.tesla.fetch_token()
class TeslaAPIHandler(): def __init__(self, bismuth, reg, unreg, op_data): self.address = "Bis1TeSLaWhTC2ByEwZnYWtsPVK5428uqnL46" self.bismuth = bismuth self.register = reg self.unregister = unreg self.op_data = op_data def fetch_vehicle_data(self, email, pwd): """ Returns a dict with vehicle data for all VINs using Tesla API """ try: data = self.tesla_data(email) except: print("Vehicle unavailable, trying again. ") data = self.tesla_data(email) N = data["count"] out = {} out["total"] = N out["vin"] = {} for i in range(0, N): vin = data["vehicle"][i]["vin"] if len(pwd) > 0: vin = (vin + pwd).encode("utf-8") vin = hashlib.sha256(vin).hexdigest() checksum = self.checksum(vin, True) vin = vin + checksum out["vin"][str(i)] = vin out[vin] = {} out[vin]["battery_type"] = data["vehicle"][i]["battery_type"] out[vin]["battery_level"] = data["vehicle"][i]["battery_level"] out[vin]["battery_range"] = data["vehicle"][i]["battery_range"] out[vin]["usable_battery_level"] = data["vehicle"][i][ "usable_battery_level"] out[vin]["charge_current_request"] = data["vehicle"][i][ "charge_current_request"] out[vin]["charge_energy_added"] = data["vehicle"][i][ "charge_energy_added"] out[vin]["charge_miles_added_ideal"] = data["vehicle"][i][ "charge_miles_added_ideal"] out[vin]["charge_miles_added_rated"] = data["vehicle"][i][ "charge_miles_added_rated"] out[vin]["ideal_battery_range"] = data["vehicle"][i][ "ideal_battery_range"] out[vin]["est_battery_range"] = data["vehicle"][i][ "est_battery_range"] out[vin]["outside_temp"] = json.dumps( data["vehicle"][i]["outside_temp"]) out[vin]["inside_temp"] = json.dumps( data["vehicle"][i]["inside_temp"]) out[vin]["odometer"] = data["vehicle"][i]["odometer"] out[vin]["timestamp"] = data["vehicle"][i]["timestamp"] out[vin]["car_version"] = data["vehicle"][i]["car_version"] out[vin]["car_type"] = data["vehicle"][i]["car_type"] out[vin]["exterior_color"] = data["vehicle"][i]["exterior_color"] out[vin]["wheel_type"] = data["vehicle"][i]["wheel_type"] return out def get_chain_data(self, asset_id, addresses, variable, filter, range_unit, temperature, startdate, enddate): """ Returns vehicle data on chain as specified by 'variable' between start and end dates """ out = {} out["x"] = [] out["y"] = [] out["z"] = [] command = "addlistopfromto" rec = self.address op = self.op_data t0 = time.mktime( datetime.datetime.strptime(startdate, "%Y-%m-%d").timetuple()) t1 = time.mktime( datetime.datetime.strptime(enddate, "%Y-%m-%d").timetuple()) t1 = t1 + 24 * 60 * 60 #Enddate Time 23:59:59 last_month = "" last_charge = 0 sum_monthly = -1 sum_distance = 0 sum_charge = 0 cycle_start = startdate cycle_end = enddate for sender in addresses.split(","): bisdata = self.bismuth.command(command, [sender, rec, op, 1, False, t0, t1]) for i in range(0, len(bisdata)): data = json.loads(bisdata[i][11]) try: vin = data["vin"]["0"] except: vin = '' if len(vin) > 0: if "monthly_" in variable: if vin == asset_id: ts = int(data[vin]["timestamp"]) / 1000 month = datetime.datetime.fromtimestamp( ts).strftime("%B %Y") if month != last_month: if sum_monthly != -1: out["x"].append(last_month) if "_efficiency" in variable: if sum_distance > 0: out["y"].append( round(1e6 * sum_charge / sum_distance) / 1e3) else: out["y"].append("Not defined") elif "_cycles" in variable: cycle_end = datetime.datetime.fromtimestamp( ts - 86400).strftime("%Y-%m-%d") cycle_data = self.get_cycle_data( asset_id, sender, "battery_level", 0, range_unit, temperature, cycle_start, cycle_end) cycle_start = datetime.datetime.fromtimestamp( ts).strftime("%Y-%m-%d") out["y"].append( cycle_data["full_cycle_equivalent"] ) else: out["y"].append( self.data_units( round(1000 * sum_monthly) / 1000, variable, range_unit, temperature)) else: last_distance = data[vin]["odometer"] sum_distance = 0 sum_charge = 0 sum_monthly = 0 sum_distance += data[vin][ "odometer"] - last_distance if data[vin]["charge_energy_added"] != last_charge: sum_charge += data[vin]["charge_energy_added"] if "monthly_distance" in variable: sum_monthly += data[vin][ "odometer"] - last_distance if "monthly_energy" in variable: if data[vin][ "charge_energy_added"] != last_charge: sum_monthly += data[vin][ "charge_energy_added"] last_distance = data[vin]["odometer"] last_charge = data[vin]["charge_energy_added"] last_month = datetime.datetime.fromtimestamp( ts).strftime("%B %Y") else: if variable == "max_range_vs_odometer": data[vin][ "max_range_vs_odometer"] = self.__get_max_range( data[vin]) if variable == "est_max_range": data[vin]["est_max_range"] = self.__get_max_range( data[vin]) ts = int(data[vin]["timestamp"]) / 1000 mydate = datetime.datetime.fromtimestamp(ts) try: if vin == asset_id: out["y"].append( self.data_units(data[vin][variable], variable, range_unit, temperature)) if "_vs_odometer" in variable: out["x"].append(data[vin]["odometer"]) else: out["x"].append( f"{mydate:%Y-%m-%d %H:%M:%S}") out["z"] = 0 except: pass if "monthly_" in variable: out["x"].append( datetime.datetime.fromtimestamp(ts).strftime("%B %Y")) if "_efficiency" in variable: if sum_distance > 0: out["y"].append( round(1e6 * sum_charge / sum_distance) / 1e3) else: out["y"].append("Not defined") elif "_cycles" in variable: cycle_end = datetime.datetime.fromtimestamp( ts + 86400).strftime("%Y-%m-%d") cycle_data = self.get_cycle_data(asset_id, sender, "battery_level", 0, range_unit, temperature, cycle_start, cycle_end) out["y"].append(cycle_data["full_cycle_equivalent"]) else: out["y"].append( self.data_units( round(1000 * sum_monthly) / 1000, variable, range_unit, temperature)) if variable == "max_range_vs_odometer": out["z_x"] = [] out["z_y"] = [] if len(out["x"]) > 2: w = mp.polyfit(out["x"], out["y"], 2, filter) data = mp.interpolate(min(out["x"]), max(out["x"]), 20, w) out["z_x"] = data["x"] out["z_y"] = data["y"] return out def get_cycle_data(self, asset_id, addresses, variable, filter, range_unit, temperature, startdate, enddate): """ Returns cycle data on chain as specified by 'variable' between start and end dates """ out = {} out["x"] = [] out["y"] = [] out["full_cycle_equivalent"] = 0 data = self.get_chain_data(asset_id, addresses, variable, filter, range_unit, temperature, startdate, enddate) try: cycles = rainflow.count_cycles(data["y"], binsize=10.0) sum = 0.0 for i in range(len(cycles)): out["x"].append("Cycle {}-{}%".format(cycles[i][0] - 10, cycles[i][0])) out["y"].append(cycles[i][1]) sum += self.full_cycle_equivalent(cycles[i]) out["full_cycle_equivalent"] = round(100 * sum) / 100 except: pass return out def myparse(self, html, search_string): L = len(search_string) i = html.find(search_string) j = html.find('"', i + L + 1) return html[i + L:j] def myparse2(self, html, search_string): L = len(search_string) i = html.find(search_string) j = html.find('&', i + L + 1) return html[i + L:j] def html_parse(self, data, html): data['_csrf'] = self.myparse(html, 'name="_csrf" value="') data['_phase'] = self.myparse(html, 'name="_phase" value="') data['_process'] = self.myparse(html, 'name="_process" value="') data['transaction_id'] = self.myparse(html, 'name="transaction_id" value="') return data def __tesla_connect(self, email): """ Connect to vehicles associated with email address """ self.tesla = Tesla(email) self.tesla.fetch_token() def tesla_vins(self, email, pwd): """ Returns all VIN numbers associated with specified email """ self.__tesla_connect(email) vehicle = self.fetch_vehicle_data(email, pwd) out = {} L = vehicle["total"] out["vehicle"] = {} out["count"] = L for i in range(0, L): vin = vehicle['vin'][str(i)] out["vehicle"][str(i)] = vin return out def tesla_data(self, email): """ Returns selected vehicle data given email. If owner has multiple vehicles, data for all of them is returned. """ S = self.__tesla_connect(email) out = {} out["vehicle"] = {} out["count"] = 0 selected = prod = self.tesla.vehicle_list() for i, product in enumerate(selected): product.sync_wake_up() out["count"] = out["count"] + 1 data = product.get_vehicle_data() battery_type = "" try: option_codes = data["option_codes"].split(",") for j in range(len(option_codes)): if option_codes[j].find("BT") == 0: battery_type = option_codes[j] break except: pass vin = data["vin"] out["vehicle"][i] = {} out["vehicle"][i]["charge_energy_added"] = data["charge_state"][ "charge_energy_added"] out["vehicle"][i]["charge_miles_added_rated"] = data[ "charge_state"]["charge_miles_added_rated"] out["vehicle"][i]["charge_miles_added_ideal"] = data[ "charge_state"]["charge_miles_added_ideal"] out["vehicle"][i]["est_battery_range"] = data["charge_state"][ "est_battery_range"] out["vehicle"][i]["ideal_battery_range"] = data["charge_state"][ "ideal_battery_range"] out["vehicle"][i]["charge_current_request"] = data["charge_state"][ "charge_current_request"] out["vehicle"][i]["usable_battery_level"] = data["charge_state"][ "usable_battery_level"] out["vehicle"][i]["battery_range"] = data["charge_state"][ "battery_range"] out["vehicle"][i]["battery_level"] = data["charge_state"][ "battery_level"] out["vehicle"][i]["timestamp"] = data["charge_state"]["timestamp"] out["vehicle"][i]["odometer"] = data["vehicle_state"]["odometer"] out["vehicle"][i]["inside_temp"] = data["climate_state"][ "inside_temp"] out["vehicle"][i]["outside_temp"] = data["climate_state"][ "outside_temp"] out["vehicle"][i]["car_version"] = data["vehicle_state"][ "car_version"] out["vehicle"][i]["battery_type"] = battery_type out["vehicle"][i]["car_type"] = data["vehicle_config"]["car_type"] out["vehicle"][i]["exterior_color"] = data["vehicle_config"][ "exterior_color"] out["vehicle"][i]["wheel_type"] = data["vehicle_config"][ "wheel_type"] out["vehicle"][i]["vin"] = vin return out def __decode_char(self, c): """ Transliteration keys, see https://en.wikipedia.org/wiki/Vehicle_identification_number """ options = { '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'J': 1, 'K': 2, 'L': 3, 'M': 4, 'N': 5, 'P': 7, 'R': 9, 'S': 2, 'T': 3, 'U': 4, 'V': 5, 'W': 6, 'X': 7, 'Y': 8, 'Z': 9 } try: w = 0 w = options[c] except: pass return w def __weight(self, pos): """ Weight factors, see https://en.wikipedia.org/wiki/Vehicle_identification_number """ options = { 0: 8, 1: 7, 2: 6, 3: 5, 4: 4, 5: 3, 6: 2, 7: 10, 8: 0, 9: 9, 10: 8, 11: 7, 12: 6, 13: 5, 14: 4, 15: 3, 16: 2, 17: 6 } try: w = 0 w = options[pos] except: pass return w def checkVIN(self, vin): """ Returns out=1 if specified VIN is valid, out=-1 otherwise. """ out = -1 try: sum = 0 N = len(vin) if N > 10: check_in = vin[8] for i in range(0, N): sum += self.__weight(i) * self.__decode_char(vin[i]) compare = sum % 11 if (compare == 11): check = 'X' else: check = str(compare) if (check_in == check): out = 1 except: pass return out def checkID(self, asset_id): """ Returns out=1 if specified asset ID is valid, out=-1 otherwise. """ out = -1 crc = self.checksum(asset_id, False) if asset_id.endswith(crc): out = 1 return out def data_units(self, data, var, range_unit, temperature): """ Returns either range or temperature with the specified unit. Range or temperature is chosen based on keyword in the string var. It is assumed that the input data is by default in either miles or Celsius. """ out = data if temperature == "F": if var.find("temp") > 0: out = round(data * 1.8 + 32.0, 1) if range_unit == "km": if (var.find("miles") > 0) or (var.find("range") > 0) or \ (var.find("meter") > 0) or (var.find("distance") > 0): out = round(data * 1.609344, 3) return out def __get_max_range(self, data): range = 0 if data["battery_level"] > 0: range = data["battery_range"] / (data["battery_level"] / 100.0) range = round(range, 3) return range def __normalize(self, out): y = out["y"] M = max(y) out["y"] = [x / M for x in y] return out def encryptDecrypt(self, input, key): N = len(key) if N > 0: out = "" M = len(input) j = 0 for i in range(M): out = out[:i] + chr(ord(input[i]) ^ ord(key[j])) j = j + 1 if j == N: j = 0 else: out = input return out def checksum(self, input, lastchar: bool = True): if lastchar == True: M = len(input) else: M = len(input) - 1 out = 0 for i in range(M): out = out + ord(input[i]) return format(out % 16, "x") def full_cycle_equivalent(self, cycle): factor = { 10: 0.043, 20: 0.086, 30: 0.155, 40: 0.225, 50: 0.400, 60: 0.500, 70: 0.600, 80: 0.700, 90: 0.850, 100: 1.0 } return round(100 * factor[cycle[0]] * cycle[1]) / 100.0