MyBMW Binding

hi, I’m trying to integrate my bmw in rules. I can connect with the api but when I ask data it gives me a an authorization fault. I’m trying to find my client_id. does somebody know where to find it?

2025-10-03 09:02:22.088 [INFO ] [org.openhab.core.model.script.BMW ] - Fetching vehicle data

2025-10-03 09:02:22.123 [INFO ] [org.openhab.core.model.script.BMW ] - State Response: {“exveErrorId”: “CU-403”,“exveErrorMsg”: “Bad Request. Forbidden”,“exveErrorRef”: “”,“exveNote”: “This application error is raised when an invalid resource is invoked, or a valid resource is invoked incorrectly.”}

2025-10-03 09:02:22.127 [INFO ] [org.openhab.core.model.script.BMW ] - Vehicle data updated successfully

Are you trying with the binding, or a script using the new CarData API? The binding will not work anymore. For the CarData API, did you follow all instructions in the CarData documentation ( CarData Customer Portal )? You need to get it configured in the portal and then go through the device code flow to setup the client.

It is not an easy flow, and from what I read, there are challenges. I haven’t finished the setup myself yet. There is already a working integration for HA ( GitHub - JjyKsi/bmw-cardata-ha ), and it has good info on the authentication setup as well in the README. So it is worth looking at that as well.

Note that the CarData API only allows 50 requests per day, but there is an MQTT stream available. The stream only seems to send info when the car sends something back. Therefore it does not update charging levels continuously when charging. It looks like the BMW app is interpolating there, something that should probably also be done in a future binding update. The HA automation already has that.

The CarData API is read only. So any functionality that was sending commands to the car cannot be supported anymore. It looks like this may be permanently broken now and will not be available in a potential future version of the binding.

You will get this when you enable the CarData API on the BMW website.

Hi Mark,

Thx for the info! I’m trying with the new cardata flow. My problem for now is that I can’t find the location to enable the api. but I will read the info you gave me! hope it brings me a step closer. If I find a way I wil post it… but for now as you can see not a big succes :slight_smile: .

And the binding is offline again :frowning:
Why is it so unreliable…

This time i can´t get the bridge back online and now it´s waiting for atleast 20 minutes because it´s out of quota.

Read the posts above …

1 Like

I think we should remove the binding for now from the official repo, just don’t know if I should just remove the whole codebase. From my point of view it would be better to create the binding from scratch and maybe take over some functionality rather than proceed working on the existing code…

2 Likes

@MartinOpenhabFan I agree. I have limited time on the next few weeks, but I am keen to get this going again. re certainly things that could be recovered though.

Hi all,

I was able to connect with MQTTX.

Do not close any tab during the set up. You will need to jump between webpages and copy from one to the other…

  1. go to the BMW CarData web portal and choose “BMW CarData”: https://www.bmw.de/de-de/mybmw/vehicle-overview
  2. click on the button “Create CarData client”, activate both switches and copy the client_ID
  3. open the CarData API Swagger webpage in a new tab: CarData Customer Portal
  4. expand “Device Flow with PKCE”
  5. expand the device code flow and click on the “try it out” button
  6. enter as client_id the client_ID from the BMW CarData web portal you copied
  7. click on execute
  8. as response you will see your user_code. Copy this! also you will get your device_code you will need later on to request your MQTT password.
  9. Go back to the BMW CarData web portal and click on the “authenticate” button
  10. a new web page will open and you have to login with your BMW user account email and password.
  11. you will be asked for a code. This is the 8-digit user_code you just copied and received from the device code flow response. It should show that you are logged in now.
  12. Go back to the BMW CarData web portal and create a CarData Stream. Select all data you want (start with a few, you can change this later on)
  13. Go back to the CarData API Swagger webpage
  14. Expand the “request a token for the device” flow
  15. Enter the client_ID you received at the beginning when you created the CarData client.
  16. Enter the device_code you find in the response of the first device code flow.
  17. click on execute and you will finally receive the password for MQTT. it is called id_token.
  18. Your username is called gcid and should be the same as in the BMW CarData web portal, when you expand the CarData streaming
  19. To test the connection in MQTTX you can follow this guideline: CarData Customer Portal

I locked/opened my BMW to trigger new data streaming.

Does anyone know if the MQTT Binding of Openhab is able to access the mqtts broker of BMW? customer.streaming-cardata.bmwgroup.com

3 Likes

Thanx for this :slight_smile: I’ll try tomorrow. I guess you could try to add the BMW mqtts broker as a new MQTT broker in the MQTT binding?

I did the setup and then tried to connect with openHAB.

The binding is trying to subscribe to topics that aren´t available and this exceeds the quota.
So now i can´t try anymore for today and need to wait for tomorrow.

How can i stop the binding from trying to subscribe to the topics from milight and homie?

info_circle 20:58:32.440	INFO	
org.openhab.core.io.transport.mqtt.reconnect.PeriodicReconnectStrategy
Try to restore connection to 'customer.streaming-cardata.bmwgroup.com'. Next attempt in 60000ms
info_circle 20:58:32.441	INFO	
openhab.event.ThingStatusInfoChangedEvent
Thing 'mqtt:broker:bmw' changed from OFFLINE (COMMUNICATION_ERROR): Server closed connection without DISCONNECT. to OFFLINE
info_circle 20:58:32.442	INFO	
org.openhab.core.io.transport.mqtt.MqttBrokerConnection
Starting MQTT broker connection to 'customer.streaming-cardata.bmwgroup.com' with clientid 1234
flag 20:58:32.534	WARN	
org.openhab.core.io.transport.mqtt.MqttBrokerConnection
Failed subscribing to topic awtrix/+/stats
info_circle 20:58:32.543	INFO	
openhab.event.ThingStatusInfoChangedEvent
Thing 'mqtt:broker:bmw' changed from OFFLINE to OFFLINE (COMMUNICATION_ERROR): Server closed connection without DISCONNECT.
flag 20:58:32.544	WARN	
org.openhab.core.io.transport.mqtt.MqttBrokerConnection
Failed subscribing to topic ruuvi/#
flag 20:58:32.544	WARN	
org.openhab.core.io.transport.mqtt.MqttBrokerConnection
Failed subscribing to topic milight/states/#
flag 20:58:32.544	WARN	
org.openhab.core.io.transport.mqtt.MqttBrokerConnection
Failed subscribing to topic +/+/$homie

You need to subscribe to your topic:

Your_user_id/+ (← this will subscribe to all your cars)

Or

Your_user_id/Your_Vin (← this will subscribe to a specific Vin)

I am currently running the Home Assistant implementation and transfer all data to mqtt to also have it available in Openhab. The HA Extension is working very well.

I can´t subscribe to a topic when the broker thing doesn´t connect.
I disabled the discovery to stop the binding from trying to subscribe to homie and milight.
But now the log tells me that the username password is wrong, even though the same data works in MQTTX.

UID: mqtt:broker:bmw
label: BMW MQTT Broker
thingTypeUID: mqtt:broker
configuration:
  lwtQos: 0
  publickeypin: true
  keepAlive: 60
  clientID: 2314
  hostnameValidated: true
  certificate: ZYX
  publickey: SHA-256:XYZ
  secure: true
  birthRetain: true
  shutdownRetain: true
  certificatepin: true
  password: 4321
  protocol: TCP
  qos: 0
  reconnectTime: 60000
  port: 9000
  mqttVersion: V5
  host: customer.streaming-cardata.bmwgroup.com
  lwtRetain: true
  username: 1234
  enableDiscovery: false

Update.
I had to create a new password again, removed the certificate and key hashes to finally get the bridge online.
Now i´m trying to get some data from the stream.
AFAIK the topic will publish all datapoints that i enabled in my BMW account under cardata streaming.

I created one generic MQTT thing and then added a channel with the username and VIN as topic.
In the channel i use the incoming value transformation to receive the data from one of the 258 datapoints.

$.data["vehicle.vehicle.travelledDistance"].value

This should extract the mileage…
Currently i can´t trigger an update as i already triggered one trough the app.

Edit: It works!
I can receive my mileage.
Now i need to create a channel for every datapoint i want to get into openHAB.

Edit 2: It works really well and you get an update almost every kilometre! Seems like a great change for now.
Atleast i wont miss the remote features as i never used them through openHAB.

1 Like

Great!

Are you able to keep it alive?

And query telematic data every 30 min?

Until now it‘s online…
What do you mean?
I don‘t know why i would need telematic data.
The MQTT pushes an update everytime the car sends an update.

Edit: Nope, the broker thing went offline :-1:
It states wrong username or password again.

Edit 2: How can we keep the conection alive when the car doesn‘t send any updates?

I think this is related to the refresh of the tokens, see CarData Customer Portal . Maybe it is possible to do following:

  1. Create the MQTT broker thing
  2. create a rule to trigger the token refresh via HTTP binding and store the tokens in an item
  3. Somehow exchange the broker password via the rule
  4. Configure the MQTT thing to retrieve all data points as channels
  5. Create the respective items

If this is possible we wouldn’t need a binding urgently

I think i was able to create a rule with Claude AI that can refresh the tokens :slight_smile:
And after that it will update the password in the MQTT broker thing!

Moved to new topic:

1 Like

Jizz they broke it and now I see it is uber complicated to make it working again. Any chance we will get some easy an automated way creating all those tokens?

I see that HA community are working on it GitHub - JjyKsi/bmw-cardata-ha

In past I used their module that was just a simple python CLI to pull the cars stats 15min. Just by running it as CLI script with added local MQTT publisher.

Even with a binding you would need to go through the initial configuration.
And that‘s alread half the work.

I try to automate the login. I have small success but now I’m stuck, maybe someone knows what my mistake is? It is in python but I’m a openhab user… The fault I get is

:cross_mark: Token request failed
Status Code: 500
Response Body: {“fault”:{“faultstring”:“Invalid Access Token”,“detail”:{“errorcode”:“keymanagement.service.invalid_access_token”}}}

import os
import base64
import hashlib
import requests
import time

# =========================================================
# CONFIGURATION
# =========================================================
BMW_DEVICE_CODE_URL = "https://customer.bmwgroup.com/gcdm/oauth/device/code"
BMW_TOKEN_URL = "https://customer.bmwgroup.com/gcdm/oauth/token"
TOKENS_FILE = "bmw_tokens.txt"

# =========================================================
# HELPER FUNCTIONS
# =========================================================
def generate_code_verifier(length: int = 64) -> str:
    """Generate PKCE code_verifier."""
    return base64.urlsafe_b64encode(os.urandom(length)).rstrip(b'=').decode('utf-8')

def generate_code_challenge(verifier: str) -> str:
    """Generate S256 code_challenge from code_verifier."""
    digest = hashlib.sha256(verifier.encode('ascii')).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b'=').decode('utf-8')

def save_tokens(tokens):
    """Save tokens to file for later use."""
    with open(TOKENS_FILE, "w") as f:
        f.write("BMW ConnectedDrive API Tokens\n")
        f.write("=" * 70 + "\n\n")
        f.write(f"Access Token: {tokens.get('access_token')}\n")
        f.write(f"Refresh Token: {tokens.get('refresh_token')}\n")
        f.write(f"ID Token: {tokens.get('id_token')}\n")
        f.write(f"Token Type: {tokens.get('token_type')}\n")
        f.write(f"Expires In: {tokens.get('expires_in')} seconds\n")
        f.write(f"Scope: {tokens.get('scope')}\n")
        f.write(f"GCID: {tokens.get('gcid')}\n")
        f.write(f"\nGenerated at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")

def load_tokens():
    """Load tokens from file if present."""
    if not os.path.exists(TOKENS_FILE):
        return None
    tokens = {}
    with open(TOKENS_FILE, "r") as f:
        for line in f:
            if ":" in line:
                key, value = line.split(":", 1)
                tokens[key.strip()] = value.strip()
    return tokens

def print_curl(url, headers, data):
    print("\n📋 Equivalent cURL (for debugging):")
    print("curl --request POST \\")
    print(f"  '{url}' \\")
    for k, v in headers.items():
        print(f"  -H '{k}: {v}' \\")
    for k, v in data.items():
        print(f"  -d '{k}={v}' \\")

def refresh_access_token(client_id, refresh_token):
    """Refresh access and ID tokens using refresh_token."""
    print("\n🔄 Refreshing token using refresh_token...")
    payload = {
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": client_id
    }
    headers = {
        "Content-Type": "application/x-www-form-urlencoded"
    }
    resp = requests.post(BMW_TOKEN_URL, data=payload, headers=headers)
    if resp.ok:
        tokens = resp.json()
        save_tokens(tokens)
        print("✅ Tokens refreshed and saved.")
        return tokens
    else:
        print("❌ Refresh failed:", resp.text)
        return None

# =========================================================
# STEP 1 — GET CLIENT ID & PKCE
# =========================================================
print("🚗 BMW CarData Device Code Flow")
print("=" * 70)

CLIENT_ID = input("Enter your BMW Client ID: ").strip()
if not CLIENT_ID:
    print("❌ Client ID is required.")
    exit(1)

# Check if refresh token exists
existing_tokens = load_tokens()
if existing_tokens and "Refresh Token" in existing_tokens:
    use_refresh = input("Found existing tokens. Refresh? (y/n): ").strip().lower()
    if use_refresh == "y":
        new_tokens = refresh_access_token(CLIENT_ID, existing_tokens["Refresh Token"])
        if new_tokens:
            exit(0)

# Generate PKCE
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)
print("\n✅ Generated code_verifier and code_challenge")
print("STEP 1 Verifier:", code_verifier)
print("STEP 1 Challenge:", code_challenge)

# =========================================================
# STEP 2 — REQUEST DEVICE CODE
# =========================================================
print("\n" + "=" * 70)
print("🔐 STEP 2: Requesting device & user code")
print("=" * 70)

device_code_payload = {
    "client_id": CLIENT_ID,
    "response_type": "device_code",
    "scope": "authenticate_user openid cardata:api:read cardata:streaming:read",
    "code_challenge": code_challenge,
    "code_challenge_method": "S256"
}

device_code_headers = {
    "Accept": "application/json",
    "Content-Type": "application/x-www-form-urlencoded"
}

resp = requests.post(BMW_DEVICE_CODE_URL, data=device_code_payload, headers=device_code_headers)

if resp.status_code != 200:
    print("❌ Device Code request failed")
    print("Status Code:", resp.status_code)
    print("Response Body:", resp.text)
    print_curl(BMW_DEVICE_CODE_URL, device_code_headers, device_code_payload)
    exit(1)

device_response = resp.json()
user_code = device_response["user_code"]
device_code = device_response["device_code"]
verification_uri = device_response.get("verification_uri_complete") or device_response.get("verification_uri")
interval = device_response.get("interval", 5)
expires_in = device_response.get("expires_in", 300)

print("\n✅ Device Code successfully retrieved!")
print("=" * 70)
print(f"📱 User Code:         {user_code}")
print(f"🔐 Device Code:       {device_code}")
print(f"🌐 Verification URI:  {verification_uri}")
print(f"⏳ Expires In:        {expires_in} seconds")
print(f"🔁 Poll Interval:     {interval} seconds")
print("=" * 70)

print("\n👉 Please open this URL in your browser to authenticate your BMW account:")
print(f"{verification_uri}")
input("⏳ Press Enter after you finished authorizing the device in the browser...")
print("STEP 2 Verifier (reused):", code_verifier)

# =========================================================
# STEP 3 — EXCHANGE DEVICE CODE FOR TOKENS
# =========================================================
print("\n" + "=" * 70)
print("🔑 STEP 3: Exchanging device code for tokens")
print("=" * 70)

token_payload = {
    "client_id": CLIENT_ID,
    "device_code": device_code,
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "code_verifier": code_verifier
}

token_headers = {
    "Content-Type": "application/x-www-form-urlencoded",
    "Accept": "application/json"
}

token_resp = requests.post(BMW_TOKEN_URL, data=token_payload, headers=token_headers)

if token_resp.status_code != 200:
    print("❌ Token request failed")
    print("Status Code:", token_resp.status_code)
    print("Response Body:", token_resp.text)
    print_curl(BMW_TOKEN_URL, token_headers, token_payload)
    exit(1)

tokens = token_resp.json()

print("\n✅ Tokens successfully received!")
print("=" * 70)
print(f"Access Token:  {tokens.get('access_token', '')[:50]}...")
print(f"Refresh Token: {tokens.get('refresh_token', '')[:50]}...")
print(f"ID Token:      {tokens.get('id_token', '')[:50]}...")
print(f"Token Type:    {tokens.get('token_type')}")
print(f"Expires In:    {tokens.get('expires_in')} seconds")
print(f"Scope:         {tokens.get('scope')}")
print(f"GCID:          {tokens.get('gcid')}")
print("=" * 70)

# Save tokens
save_tokens(tokens)
print(f"💾 Tokens saved to {TOKENS_FILE}")

# =========================================================
# STEP 4 — TOKEN USAGE INSTRUCTIONS
# =========================================================
print("\n📚 Token Usage Guide")
print("=" * 70)
print("🔹 Access Token:")
print("   - Use for BMW CarData REST API calls")
print("   - Header: Authorization: Bearer <access_token>")
print("   - Valid for 1 hour")
print()
print("🔹 ID Token:")
print("   - Use for streaming vehicle data")
print("   - Valid for 1 hour")
print()
print("🔹 Refresh Token:")
print("   - Valid for 2 weeks")
print("   - Use to get new tokens without re-authenticating")
print("=" * 70)

# =========================================================
# STEP 5 — TEST API CALL (EXAMPLE)
# =========================================================
print("\n🧪 Testing API Access")
print("=" * 70)

# Extract the access token
access_token = tokens.get('access_token')

# Set up headers with Bearer token
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json"
}

# Example: Get vehicles list (adjust the endpoint based on BMW API documentation)
api_url = "https://cardata.bmwgroup.com/api/v1/vehicles"  # Replace with actual endpoint

try:
    api_response = requests.get(api_url, headers=headers)

    if api_response.status_code == 200:
        print("✅ API call successful!")
        print("Response:", api_response.json())
    else:
        print(f"❌ API call failed with status {api_response.status_code}")
        print("Response:", api_response.text)
except Exception as e:
    print(f"❌ Error making API call: {e}")

print("=" * 70)
1 Like