Skip to content
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

No Vehicles Found #3

Open
Benjoyo opened this issue Aug 20, 2022 · 21 comments
Open

No Vehicles Found #3

Benjoyo opened this issue Aug 20, 2022 · 21 comments

Comments

@Benjoyo
Copy link

Benjoyo commented Aug 20, 2022

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

@kaedenbrinkman
Copy link
Owner

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?

@Benjoyo
Copy link
Author

Benjoyo commented Sep 14, 2022

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.

@kaedenbrinkman
Copy link
Owner

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

@Benjoyo
Copy link
Author

Benjoyo commented Sep 17, 2022

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... 🤔

@kaedenbrinkman
Copy link
Owner

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
Copy link
Owner

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 :)

@Benjoyo
Copy link
Author

Benjoyo commented Sep 19, 2022

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?

@kaedenbrinkman
Copy link
Owner

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

@Benjoyo
Copy link
Author

Benjoyo commented Sep 20, 2022

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
Copy link
Author

Benjoyo commented Sep 25, 2022

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.

@runasy-koonta
Copy link

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

@kaedenbrinkman
Copy link
Owner

@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.

@runasy-koonta
Copy link

@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

@kaedenbrinkman
Copy link
Owner

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.

@runasy-koonta
Copy link

Okay, thanks for support!

@zhy2020
Copy link

zhy2020 commented Jan 28, 2023

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

@kaedenbrinkman
Copy link
Owner

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?

@zhy2020
Copy link

zhy2020 commented Jan 29, 2023

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

@zhy2020
Copy link

zhy2020 commented Jan 29, 2023

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

9c8261d0af20c8fdc8d90a54ffd3942

@kaedenbrinkman
Copy link
Owner

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.

@zhy2020
Copy link

zhy2020 commented Jan 31, 2023

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants