PyTeslaBLE icon indicating copy to clipboard operation
PyTeslaBLE copied to clipboard

No Vehicles Found

Open Benjoyo opened this issue 3 years ago • 21 comments

When I try the example script, I am not able to find my car.

I tried different locations, car locked/unlocked, outside/inside the car.

Any idea what can cause a car not to be found?

2021 MacBook Pro M1 (Bluetooth access granted) 2022 made in Germany Model Y Long Range 2022.15.103

Benjoyo avatar Aug 20 '22 17:08 Benjoyo

Unfortunately I do not own any Apple devices for testing but the BLE library I am using is officially supported on macOS 10.15+ (except 12.0, 12.1 and 12.2). What version of macOS are you using?

kaedenbrinkman avatar Aug 28 '22 01:08 kaedenbrinkman

I'm on 12.5.1 (21G83). I don't have any non Apple devices to try 🫣 Have to see if I can get one.

Benjoyo avatar Sep 14 '22 13:09 Benjoyo

Currently I am identifying vehicles by their manufacturer data - it is possible your device reports this differently or your vehicle has diffent manufacturer data.

Can you find the BLE address of your vehicle? Try something like this:

import simplepyble
adapters = simplepyble.Adapter.get_adapters()

time=5000

if len(adapters) == 0:
    print("No adapters found")
elif len(adapters) == 1:
    adapter = adapters[0]
else:
    # Query the user to pick an adapter
    print("Please select an adapter:")
    for i, adapter in enumerate(adapters):
        print(f"{i}: {adapter.identifier()} [{adapter.address()}]")

    choice = int(input("Enter choice: "))
    adapter = adapters[choice]

adapter.scan_for(time)
peripherals = adapter.scan_get_results()
for i, peripheral in enumerate(peripherals):
    print(f"{i}: {peripheral.identifier()} [{peripheral.address()}]")

Your identifier should be an S followed by the first 8 bytes of the SHA1 hash of your vin - something like Sa6bab0d54ffaecf1

kaedenbrinkman avatar Sep 14 '22 19:09 kaedenbrinkman

Thanks, this got me this address: EA8A64D9-882E-73A1-5670-E8C987EFA4F6

Identifier starts with an S, like you said. Do you need that too? Not sure how private I should keep that info... 🤔

Benjoyo avatar Sep 17 '22 10:09 Benjoyo

Awesome - looks like the issue is that your Bluetooth adapter doesn't seem to be returning the manufacturer data like I was expecting. This could be because the Made in Berlin cars have different manufacturer data, or that your MacBook doesn't pick it up. You can still use the library, though! You'll need to do the scan manually and construct the Vehicle object from the peripheral and your generated private key file. Try something like this:

from pyteslable import BLE
from pyteslable import Vehicle
import simplepyble

adapters = simplepyble.Adapter.get_adapters()
tesla_ble = BLE("private_key.pem")
private_key = tesla_ble.getPrivateKey()

time=5000

if len(adapters) == 0:
    print("No adapters found")
elif len(adapters) == 1:
    adapter = adapters[0]
else:
    # Query the user to pick an adapter
    print("Please select an adapter:")
    for i, adapter in enumerate(adapters):
        print(f"{i}: {adapter.identifier()} [{adapter.address()}]")

    choice = int(input("Enter choice: "))
    adapter = adapters[choice]

adapter.scan_for(time)
peripherals = adapter.scan_get_results()
for i, peripheral in enumerate(peripherals):
        print(f"{i}: {peripheral.identifier()} [{peripheral.address()}]")

choice = int(input("Enter choice: "))
peripheral = peripherals[choice]


vehicle = Vehicle(peripheral, private_key)

# now we can connect to the vehicle

if (vehicle != None):
    print("Connecting to vehicle...")
    vehicle.connect()
    # vehicle.debug()

    if not vehicle.isConnected():
        print("Vehicle failed to connect")
        exit()

    if not vehicle.isAdded():
        print("Tap your keycard on the center console")
        vehicle.whitelist()
    
    # Print closure status of all doors when they change
    vehicle.onStatusChange(lambda vehic: print(f"\nStatus update: {vehic.status()}\n"))

    # Request status
    vehicle.vehicle_status()

    command = ""
    while True:
        print("Enter a command, or 'help' for a list of commands. Type 'exit' to quit.")
        command = input("Enter command: ")
        command = command.upper().replace(' ', '_')
        if command == "LOCK":
            vehicle.lock()
        elif command == "UNLOCK":
            vehicle.unlock()
        elif command == "OPEN_TRUNK":
            vehicle.open_trunk()
        elif command == "OPEN_FRUNK":
            vehicle.open_frunk()
        elif command == "OPEN_CHARGE_PORT":
            vehicle.open_charge_port()
        elif command == "CLOSE_CHARGE_PORT":
            vehicle.close_charge_port()
        elif command == "EXIT":
            break
        elif command == "HELP":
            print("\n\n\nCommands available:")
            print("\tEXIT: Exit the program")
            print("\tHELP: Print this message")
            print("\tLOCK: Lock the vehicle")
            print("\tUNLOCK: Unlock the vehicle")
            print("\tOPEN_TRUNK: Open the vehicle's trunk")
            print("\tOPEN_FRUNK: Open the vehicle's front trunk")
            print("\tOPEN_CHARGE_PORT: Open and unlock the vehicle's charge port")
            print("\tCLOSE_CHARGE_PORT: Close and lock the vehicle's charge port")
            print("\n\n")
        else:
            print("Unknown command")
    print("Disconnecting...")
    vehicle.disconnect()
    print("Vehicle disconnected successfully")
else:
    print("Vehicle not found")
    exit()

I haven't tested the above script, but I think it should work. You just need to select your vehicle manually after the scan happens.

kaedenbrinkman avatar Sep 17 '22 17:09 kaedenbrinkman

Also, your identifier is about the same as your VIN in terms of how confidential it is. You can calculate it by doing a simple SHA1 hash of the VIN, and likewise you can find your VIN from it by creating a rainbow table of the probable VIN ranges.

So I would not recommend posting the identifier online, but note that anyone can find it by looking at the VIN displayed on your dash or by doing a BLE scan near your vehicle :)

kaedenbrinkman avatar Sep 17 '22 17:09 kaedenbrinkman

Thank you for the detailed support, that worked :)

Now I am stuck one step further and not sure if that is also related to unexpected characteristics of my car or not: tapping my key card does not work. With debug enabled I get the following output in a loop:

2022-09-19 19:57:18.730 Python[28276:4329786] Characteristic does not support write without response.
Waiting for keycard to be tapped...
authenticationRequest {
  requestedLevel: AUTHENTICATION_LEVEL_DRIVE
}

Tapping the card at that time has no effect.

I found a similar message in the docs but can't really make sense of it. What message do we expect here?

Benjoyo avatar Sep 19 '22 19:09 Benjoyo

Looks like there was a bug introduced in v0.1.3 that prevented the library from being able to receive the shared key from the vehicle. Just fixed it, try:

pip install pyteslable==0.1.4

kaedenbrinkman avatar Sep 20 '22 03:09 kaedenbrinkman

That doesn't work unfortunately. I had the same idea and already tried what you did in 0.1.4.

But that doesn't help, as I only get authenticationRequest messages, nothing else.

authenticationRequest {
  requestedLevel: AUTHENTICATION_LEVEL_DRIVE
}

Due to these authenticationRequest messages it now raises an exception (Car's ephermeral key not yet loaded!) now that the if statement is gone. That's because it tries to send a signed message (AuthenticationResponse), however the docs say it should be unsigned? In that case there would be no exception. What is correct here?

But the main issue is something different, as I just found out. On every write I get a warning like:

2022-09-21 17:57:44.408 Python[1136:4747434] Characteristic does not support write without response.

Which I ignored in the beginning. After some investigation, it turns out that there is some specific behavior to BLE on macOS that results in all messages given to write_command being dropped. The problem seems to be that write_command will set WRITE_TYPE_NO_RESPONSE - which the characteristic does not support. I assume Android, Linux etc. will just ignore that and send anyway - macOS however issues a warning and drops the message.

So no wonder that nothing worked for me. If I swap write_command with write_request in the whitelist function, I am able to successfully receive the OPERATIONSTATUS_WAIT, then tap my card and receive OPERATIONSTATUS_OK and can see the key added in the car. However I still can't successfully complete the whitelist function as it doesn't leave the loop. Need to do some more debugging tomorrow.

Benjoyo avatar Sep 20 '22 11:09 Benjoyo

So I was able to get everything working by swapping the BLE lib with the bleak library. This is a workaround: while using write_request instead of write_command did get me a step further (messages are actually being sent and a response received), the whole app froze during whitelisting. Seems to be a bug in the BLE library on macOS. With bleak everything is working fine.

You may close the issue now, thanks for your support. However, I still think it's a bug that handle_notify calls authenticationRequest without a check, as it requires vehicle_key to be set.

Benjoyo avatar Sep 25 '22 17:09 Benjoyo

@Benjoyo Can you share the code that you swapped the BLE lib with the bleak library? I'm having same issue.

runasy-koonta avatar Oct 06 '22 03:10 runasy-koonta

@runasy-koonta Have you tried doing something like this? If you are on macOS you may need to manually select the correct peripheral.

Awesome - looks like the issue is that your Bluetooth adapter doesn't seem to be returning the manufacturer data like I was expecting. This could be because the Made in Berlin cars have different manufacturer data, or that your MacBook doesn't pick it up. You can still use the library, though! You'll need to do the scan manually and construct the Vehicle object from the peripheral and your generated private key file. Try something like this:

from pyteslable import BLE
from pyteslable import Vehicle
import simplepyble

adapters = simplepyble.Adapter.get_adapters()
tesla_ble = BLE("private_key.pem")
private_key = tesla_ble.getPrivateKey()

time=5000

if len(adapters) == 0:
    print("No adapters found")
elif len(adapters) == 1:
    adapter = adapters[0]
else:
    # Query the user to pick an adapter
    print("Please select an adapter:")
    for i, adapter in enumerate(adapters):
        print(f"{i}: {adapter.identifier()} [{adapter.address()}]")

    choice = int(input("Enter choice: "))
    adapter = adapters[choice]

adapter.scan_for(time)
peripherals = adapter.scan_get_results()
for i, peripheral in enumerate(peripherals):
        print(f"{i}: {peripheral.identifier()} [{peripheral.address()}]")

choice = int(input("Enter choice: "))
peripheral = peripherals[choice]


vehicle = Vehicle(peripheral, private_key)

# now we can connect to the vehicle

if (vehicle != None):
    print("Connecting to vehicle...")
    vehicle.connect()
    # vehicle.debug()

    if not vehicle.isConnected():
        print("Vehicle failed to connect")
        exit()

    if not vehicle.isAdded():
        print("Tap your keycard on the center console")
        vehicle.whitelist()
    
    # Print closure status of all doors when they change
    vehicle.onStatusChange(lambda vehic: print(f"\nStatus update: {vehic.status()}\n"))

    # Request status
    vehicle.vehicle_status()

    command = ""
    while True:
        print("Enter a command, or 'help' for a list of commands. Type 'exit' to quit.")
        command = input("Enter command: ")
        command = command.upper().replace(' ', '_')
        if command == "LOCK":
            vehicle.lock()
        elif command == "UNLOCK":
            vehicle.unlock()
        elif command == "OPEN_TRUNK":
            vehicle.open_trunk()
        elif command == "OPEN_FRUNK":
            vehicle.open_frunk()
        elif command == "OPEN_CHARGE_PORT":
            vehicle.open_charge_port()
        elif command == "CLOSE_CHARGE_PORT":
            vehicle.close_charge_port()
        elif command == "EXIT":
            break
        elif command == "HELP":
            print("\n\n\nCommands available:")
            print("\tEXIT: Exit the program")
            print("\tHELP: Print this message")
            print("\tLOCK: Lock the vehicle")
            print("\tUNLOCK: Unlock the vehicle")
            print("\tOPEN_TRUNK: Open the vehicle's trunk")
            print("\tOPEN_FRUNK: Open the vehicle's front trunk")
            print("\tOPEN_CHARGE_PORT: Open and unlock the vehicle's charge port")
            print("\tCLOSE_CHARGE_PORT: Close and lock the vehicle's charge port")
            print("\n\n")
        else:
            print("Unknown command")
    print("Disconnecting...")
    vehicle.disconnect()
    print("Vehicle disconnected successfully")
else:
    print("Vehicle not found")
    exit()

I haven't tested the above script, but I think it should work. You just need to select your vehicle manually after the scan happens.

kaedenbrinkman avatar Oct 06 '22 04:10 kaedenbrinkman

@runasy-koonta Have you tried doing something like this? If you are on macOS you may need to manually select the correct peripheral.

Awesome - looks like the issue is that your Bluetooth adapter doesn't seem to be returning the manufacturer data like I was expecting. This could be because the Made in Berlin cars have different manufacturer data, or that your MacBook doesn't pick it up. You can still use the library, though! You'll need to do the scan manually and construct the Vehicle object from the peripheral and your generated private key file. Try something like this:

from pyteslable import BLE
from pyteslable import Vehicle
import simplepyble

adapters = simplepyble.Adapter.get_adapters()
tesla_ble = BLE("private_key.pem")
private_key = tesla_ble.getPrivateKey()

time=5000

if len(adapters) == 0:
    print("No adapters found")
elif len(adapters) == 1:
    adapter = adapters[0]
else:
    # Query the user to pick an adapter
    print("Please select an adapter:")
    for i, adapter in enumerate(adapters):
        print(f"{i}: {adapter.identifier()} [{adapter.address()}]")

    choice = int(input("Enter choice: "))
    adapter = adapters[choice]

adapter.scan_for(time)
peripherals = adapter.scan_get_results()
for i, peripheral in enumerate(peripherals):
        print(f"{i}: {peripheral.identifier()} [{peripheral.address()}]")

choice = int(input("Enter choice: "))
peripheral = peripherals[choice]


vehicle = Vehicle(peripheral, private_key)

# now we can connect to the vehicle

if (vehicle != None):
    print("Connecting to vehicle...")
    vehicle.connect()
    # vehicle.debug()

    if not vehicle.isConnected():
        print("Vehicle failed to connect")
        exit()

    if not vehicle.isAdded():
        print("Tap your keycard on the center console")
        vehicle.whitelist()
    
    # Print closure status of all doors when they change
    vehicle.onStatusChange(lambda vehic: print(f"\nStatus update: {vehic.status()}\n"))

    # Request status
    vehicle.vehicle_status()

    command = ""
    while True:
        print("Enter a command, or 'help' for a list of commands. Type 'exit' to quit.")
        command = input("Enter command: ")
        command = command.upper().replace(' ', '_')
        if command == "LOCK":
            vehicle.lock()
        elif command == "UNLOCK":
            vehicle.unlock()
        elif command == "OPEN_TRUNK":
            vehicle.open_trunk()
        elif command == "OPEN_FRUNK":
            vehicle.open_frunk()
        elif command == "OPEN_CHARGE_PORT":
            vehicle.open_charge_port()
        elif command == "CLOSE_CHARGE_PORT":
            vehicle.close_charge_port()
        elif command == "EXIT":
            break
        elif command == "HELP":
            print("\n\n\nCommands available:")
            print("\tEXIT: Exit the program")
            print("\tHELP: Print this message")
            print("\tLOCK: Lock the vehicle")
            print("\tUNLOCK: Unlock the vehicle")
            print("\tOPEN_TRUNK: Open the vehicle's trunk")
            print("\tOPEN_FRUNK: Open the vehicle's front trunk")
            print("\tOPEN_CHARGE_PORT: Open and unlock the vehicle's charge port")
            print("\tCLOSE_CHARGE_PORT: Close and lock the vehicle's charge port")
            print("\n\n")
        else:
            print("Unknown command")
    print("Disconnecting...")
    vehicle.disconnect()
    print("Vehicle disconnected successfully")
else:
    print("Vehicle not found")
    exit()

I haven't tested the above script, but I think it should work. You just need to select your vehicle manually after the scan happens.

Yes, but shows this message. 2022-10-06 13:31:03.533 python[92662:7229584] Characteristic does not support write without response.

Also, there is an error: Traceback (most recent call last): File "/Users/minjunkaaang/PycharmProjects/PyTesla/main.py", line 49, in vehicle.whitelist() File "/Users/minjunkaaang/opt/anaconda3/lib/python3.9/site-packages/pyteslable/TeslaBLE.py", line 233, in whitelist self.__peripheral.write_command( RuntimeError: The requested operation is not supported.

My environment: MacOS 12.4 (M1) Model 3 with Atom (2021) 2022.24.8

runasy-koonta avatar Oct 06 '22 04:10 runasy-koonta

Yes, looks like this is the same issue as @Benjoyo had. You can try cloning the repo, changing write_command to write_request, and then repackaging and installing it locally - it sounds like @Benjoyo had some success with this. I can change the official library to do this and release a new version, although I'll need to test it first and I am relatively busy this week so it might take a little while.

kaedenbrinkman avatar Oct 06 '22 04:10 kaedenbrinkman

Okay, thanks for support!

runasy-koonta avatar Oct 06 '22 04:10 runasy-koonta

hi, When I use this code, I get these errors 43bc05511895cd371e2ff21cb58e3db 45a4192d634edc45835e10d28754472 45a4192d634edc45835e10d28754472

zhy2020 avatar Jan 28 '23 14:01 zhy2020

The issue in your first picture happens when your device tries to send a signedToMsg before it has been whitelisted. In the second picture it looks like you got disconnected for some reason.

What does your code look like? Are you using the example in the README or have you written your own code?

kaedenbrinkman avatar Jan 28 '23 16:01 kaedenbrinkman

I'm using the code in the 'example/Main.py'.

zhy2020 avatar Jan 29 '23 01:01 zhy2020

When I changed write_command to write_request, the program reported an error

9c8261d0af20c8fdc8d90a54ffd3942

zhy2020 avatar Jan 29 '23 14:01 zhy2020

See https://github.com/kaedenbrinkman/PyTeslaBLE/#cryptography-library-modification If you modify the line in /usr/lib/python3/dist-packages/cryptography/hazmat/primitives/ciphers/aead.py line 174, you should be able to remove the statement that produces the error. Or, I think you can use an older version of cryptography.

kaedenbrinkman avatar Jan 31 '23 01:01 kaedenbrinkman

Thanks

zhy2020 avatar Jan 31 '23 14:01 zhy2020