Connect to new BMW CarData service through MQTT

Hi there,

based on the discussion in the topic of the now deprecated MyBMW binding, i created a solution to receive data through the new MQTT datastream.

0. BMW MQTT Integration

Prerequisites:

  • BMW Connected Drive account
  • openHAB 5.0.1
  • MQTT binding
  • Telegram binding (optional for the refresh, mandatory for the setup)

There are several steps to get this running and you need to ensure all prerequisites are met.

  1. Create a Client_ID and get your tokens
    1. Manual setup in the BMW portals
    2. Setup with openHAB and Telegram
  2. Create a MQTT Broker and Generic thing
    2.1. Create channels in the Genering thing for your items
  3. Create the JSRules to refresh the tokens and update your Broker thing
  4. Create more channels :slight_smile:

Method 1.2 is using items to store your tokens and start the process.

1.1 BMW Portal

Please read ALL steps before you start and make sure you do NOT close any browser tabs/windows in the process.

  1. Open the vehicle overview and select “BMW CarData”
    https://www.bmw.de/de-de/mybmw/vehicle-overview

  2. Under “Technical access to BMW CarData”, select “Create CarData Client”
    Copy the Client_ID and enable both switches — wait a moment before activating the second one.
    If you receive an error message, close the tab and reload the page.

  3. Open the “CarData Customer Portal” and select “Device Code Flow API”
    CarData Customer Portal

  4. Expand “Device Flow with PKCE” and then expand “gcdm/oauth/device/code” (Starts the device code flow)

  5. Click “Try it out”, insert the Client_ID from step 2, and click Execute.

  6. Further down, the response with code 200 should appear under Responses.
    Here you will find two additional pieces of information: the User_Code and the Device_Code — copy both again.
    These values are required for authorization and for requesting the MQTT password.

  7. In the tab from step 2, click “Authenticate device”, log in with your ConnectedDrive account, and enter the User_Code from step 6 in the new tab.
    The entry should be confirmed with “Device authorized”.

  8. Go back to the BMW CarData page, scroll down to “CarData Streaming”, and create a stream.

  9. Expand the connection details and copy the username (gcid).

  10. Under “Change data selection”, you can now select all the required data.
    Note: There are over 200 data points, though not all are supported by every vehicle.
    If you like, you can automate this via the browser console — see the script at the end of this section.

  11. Go back to the “CarData Customer Portal” and expand “/gcdm/oauth/token” (Request a token for the device).

  12. Click “Try it out”, insert your Client_ID and Device_Code, and confirm by clicking Execute.
    You should again see a response with Response Code 200.

  13. The response now contains several IDs and tokens:

  • gcid = username, identical to step 9
  • access_token = used to access the CarData API
  • refresh_token = token used to renew all tokens
  • id_token = the password for MQTT

Finally, you can test the data using the MQTTX Client.
Here’s a quick guide:

For the Subscriber, use your gcid/VIN, e.g.
12345678-1234-1234-123456789012/WBA1AB23456CD78901

Then, trigger an update via the MyBMW app, for example by locking the vehicle.
You should then receive the data packets.


Script to automate the activation of all data points.
Note: It takes some time after you hit enter and the tab may get unresponsive, just wait.

(() => {
  const labels = document.querySelectorAll('.css-k008qs label.chakra-checkbox');
  let changed = 0;

  labels.forEach(label => {
    const input = label.querySelector('input.chakra-checkbox__input[type="checkbox"]');
    if (!input || input.disabled || input.checked) return;

    label.click();
    if (!input.checked) {
      const ctrl = label.querySelector('.chakra-checkbox__control');
      if (ctrl) ctrl.click();
    }
    if (!input.checked) {
      input.checked = true;
      ['click', 'input', 'change'].forEach(type =>
        input.dispatchEvent(new Event(type, { bubbles: true }))
      );
    }
    if (input.checked) changed++;
  });

  console.log(`Checked ${changed} of ${labels.length} checkboxes.`);
})();

Script from GitHub - JjyKsi/bmw-cardata-ha

1.2 openHAB Telegram

This rule allows you to automatically do the steps 3 to 13 of the manual setup.
You need to input your Client_ID into the item ‘carBMW_Client_ID’ as this will trigger the setup.
After that you´ll receive two telegram messages, one with the link to authorize your device code and one to confirm you did.
The rule will also store the expire time of your tokens into the same item that is used for the refresh.

BMW_Token_Setup.js
Enter your telegram data in line 22 to 25!

/**
 * BMW Token Initial Setup Rule
 * Creates initial tokens using Device Code Flow
 * Last update: 26.10.2025 - 12:20
 */

// Global configuration
var TELEGRAM_NOTIFY_ERROR = true; // Send Telegram notification on errors

// Item configuration
var refreshTokenExpiryItem = 'carBMW_Expire';
var clientIDItem = 'carBMW_Client_ID';
var codeVerifierItem = 'carBMW_Code_Verifier';
var codeChallengeItem = 'carBMW_Code_Challenge';
var deviceCodeItem = 'carBMW_Code_Device';
var userCodeItem = 'carBMW_Code_User';
var tokenRequestSwitch = 'carBMW_Token_Request';
var tokenAccessItem = 'carBMW_Token_Access';
var tokenRefreshItem = 'carBMW_Token_Refresh';
var tokenIdItem = 'carBMW_Token_ID';

// Telegram configuration
var bot1 = 1234; // Enter your telegram bot ID
var tgAddOnId = 'telegram:telegramBot:XYZ'; // Enter your telegram binding ID
var tgReplyItem = 'tgReplyId'; // Item that receives the reply ID
var tgLastMessageItem = 'tgLastMessageText'; // Item that receives the last message text

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// please don't change the code from here
//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
var telegramAction = actions.get('telegram', tgAddOnId);
var refreshTokenExpiry = items.getItem(refreshTokenExpiryItem);
//var tokenAccess = items.getItem(tokenAccessItem);
//var tokenRefresh = items.getItem(tokenRefreshItem);
//var tokenId = items.getItem(tokenIdItem);
// Function to escape special characters for Telegram
function escapeTelegram(text) {
    if (text === null || text === undefined) {
        return "";
    }
    text = text.toString(); // Sicherheit für Number/Boolean-Werte
    if (text.includes('{') || text.includes('[')) {
        // Setze in Code-Block (dann kein Escaping nötig)
        return '`' + text + '`';
    }
    // Sonst normale Zeichen escapen
    return text.replace(/[_*`[\]]/g, '\\$&');
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Function to generate random code_verifier (32 random bytes as Base64url)
function generateCodeVerifier() {
    var SecureRandom = Java.type("java.security.SecureRandom");
    var Base64 = Java.type("java.util.Base64");
    
    var random = new SecureRandom();
    var bytes = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 32);
    random.nextBytes(bytes);
    
    // Base64url encoding (without padding)
    return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Function to generate code_challenge from code_verifier (SHA-256 + Base64url)
function generateCodeChallenge(verifier) {
    var MessageDigest = Java.type("java.security.MessageDigest");
    var Base64 = Java.type("java.util.Base64");
    var StandardCharsets = Java.type("java.nio.charset.StandardCharsets");
    
    var digest = MessageDigest.getInstance("SHA-256");
    var hash = digest.digest(verifier.getBytes(StandardCharsets.UTF_8));
    
    // Base64url encoding (without padding)
    return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Function to enode the header parameters for sendHttpPostRequest
function encodeParam(key, value) {
    return java.net.URLEncoder.encode(key, "UTF-8") + "=" + java.net.URLEncoder.encode(value, "UTF-8");
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Function to convert EPOCH to DateTime
function formatDate(epochMillis) {
    var date = new Date(epochMillis);

    var day = String(date.getDate()).padStart(2, '0');
    var month = String(date.getMonth() + 1).padStart(2, '0');
    var year = date.getFullYear();
    var hours = String(date.getHours()).padStart(2, '0');
    var minutes = String(date.getMinutes()).padStart(2, '0');
    var seconds = String(date.getSeconds()).padStart(2, '0');

    return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`;
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Step 1: Start Device Code Flow
rules.JSRule({
    name: "BMW Token Setup - Step 1: Device Code Request",
    description: "Requests device code and user code when Client ID changes",
    triggers: [
        triggers.ItemStateUpdateTrigger(clientIDItem)
    ],
    execute: function(event) {
        var logger = log("BMW_Token_Setup_Step1");
                
        try {
            var clientId = items.getItem(clientIDItem).state.toString();
            
            // Check if Client ID is valid
            if (clientId === "NULL" || clientId === "UNDEF" || clientId === "") {
                logger.warn("Client ID is empty. Cannot start Device Code Flow.");
                return;
            }
            
            logger.info("Starting BMW Device Code Flow for Client ID: " + clientId);
            
            // Generate code_verifier and code_challenge
            var codeVerifier = generateCodeVerifier();
            var codeChallenge = generateCodeChallenge(codeVerifier);
            
            // Store verifier and challenge in items
            items.getItem(codeVerifierItem).postUpdate(codeVerifier);
            items.getItem(codeChallengeItem).postUpdate(codeChallenge);
            
            logger.info("Generated code_verifier and code_challenge");
            logger.info("Code Verifier: " + codeVerifier);
            logger.info("Code Challenge: " + codeChallenge);
            
            // Prepare HTTP POST request with URL encoding
            var url = "https://customer.bmwgroup.com/gcdm/oauth/device/code";
            
            // URL encode the scope parameter (spaces must be encoded as %20 or +)
            var scopeValue = "authenticate_user openid cardata:streaming:read cardata:api:read";
            var encodedScope = java.net.URLEncoder.encode(scopeValue, "UTF-8");

            // Build the encoded payload
            var payload = [
                encodeParam("client_id", clientId),
                encodeParam("response_type", "device_code"),
                encodeParam("scope", scopeValue),
                encodeParam("code_challenge", codeChallenge),
                encodeParam("code_challenge_method", "S256")
            ].join("&");
            
            // Create headers as Java HashMap
            var HashMap = Java.type("java.util.HashMap");
            var headers = new HashMap();
            headers.put("Accept", "application/json");
                      
            // Execute HTTP request
            var response = actions.HTTP.sendHttpPostRequest(url, "application/x-www-form-urlencoded", payload, headers, 10000);
            
            // Check if response is valid
            if (response === null || response === undefined || response === "") {
                logger.error("No valid response received from BMW.");
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Setup Failed\n\n" +
                        "No valid response received from BMW during Device Code request.");
                }
                return;
            }
            
            logger.info("Received response: " + response);
            
            // Parse JSON response
            var jsonResponse = JSON.parse(response);
            
            // Check for errors
            if (jsonResponse.error) {
                var errorMsg = jsonResponse.error_description || jsonResponse.error;
                var errorCode = jsonResponse.error;
                
                logger.error("BMW API Error Code: " + errorCode);
                logger.error("BMW API Error Description: " + errorMsg);
                logger.error("Full JSON Response: " + response);
                
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Setup Failed\n\n" +
                        "BMW API Error during Device Code request\n\n" +
                        "Error Code: " + escapeTelegram(errorCode) + "\n" +
                        "Description: " + escapeTelegram(errorMsg));
                }
                return;
            }
            
            // Check if all required fields are present
            if (!jsonResponse.user_code || !jsonResponse.device_code || !jsonResponse.verification_uri_complete) {
                logger.error("Incomplete response received. Missing required fields.");
                logger.error("Response: " + response);
                
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Setup Failed\n\n" +
                        "Incomplete response received from BMW.\n\n" +
                        "Response: " + escapeTelegram(response));
                }
                return;
            }
            
            // Store device code and user code in items
            items.getItem(deviceCodeItem).postUpdate(jsonResponse.device_code);
            items.getItem(userCodeItem).postUpdate(jsonResponse.user_code);
            
            logger.info("Device Code and User Code successfully received and stored.");
            logger.info("User Code: " + jsonResponse.user_code);
            logger.info("Verification URI: " + jsonResponse.verification_uri_complete);
            
            // Send verification URI via Telegram
            if (telegramAction !== null) {
                telegramAction.sendTelegram(bot1, "🔐 BMW Token Setup - Step 1\n\n" +
                    "Please verify your Client ID by opening this link:\n\n" +
                    escapeTelegram(jsonResponse.verification_uri_complete) + "\n\n" +
                    "User Code: " + escapeTelegram(jsonResponse.user_code) + "\n\n" +
                    "This link expires in " + jsonResponse.expires_in + " seconds (" + Math.floor(jsonResponse.expires_in / 60) + " minutes).");
                
                // Wait a moment before sending the question
                java.lang.Thread.sleep(2000);
                
                // Send question via Telegram
                telegramAction.sendTelegramQuery(bot1, 
                    "Have you completed the verification?\n\nShould I request the tokens now?",
                    "BMW_Token_Request",
                    "Yes", "No");
            }
            
        } catch (error) {
            logger.error("Exception during Device Code request: " + error);
            logger.error("Error type: " + error.name);
            logger.error("Error message: " + error.message);
            logger.error("Stack trace: " + error.stack);
            
            if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                telegramAction.sendTelegram(bot1, "❌ BMW Token Setup Exception\n\n" +
                    "Exception occurred during Device Code request.\n\n" +
                    "Error: " + escapeTelegram(error.toString()) + "\n" +
                    "Type: " + escapeTelegram(error.name) + "\n" +
                    "Message: " + escapeTelegram(error.message));
            }
        }
    }
});
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Step 1.5: Handle Telegram reply
rules.JSRule({
    name: "BMW Token Setup - Step 1.5: Handle Telegram Reply",
    description: "Processes the Telegram reply about verification completion",
    triggers: [
        triggers.ItemStateChangeTrigger(tgReplyItem, undefined, "BMW_Token_Request")
    ],
    execute: function(event) {
        var logger = log("BMW_Token_Setup_Reply");
        
        var replyId = items.getItem(tgReplyItem).state.toString();
        var replyText = items.getItem(tgLastMessageItem).state.toString();
        
        try {
            logger.info("Received Telegram reply: " + replyText);
            
            if (replyId === "BMW_Token_Request") {
                if (replyText === "Yes") {
                    logger.info("User confirmed verification. Setting " + tokenRequestSwitch.toString() + " to ON.");
                    
                    // Set item to trigger token request
                    items.getItem(tokenRequestSwitch).postUpdate("ON");
                    
                    // Send acknowledgement
                    if (telegramAction !== null) {
                        telegramAction.sendTelegramAnswer(bot1, "BMW_Token_Request", "✅ Starting token request...");
                    }
                } else if (replyText === "No") {
                    logger.info("User declined. Token request cancelled.");
                    
                    // Send acknowledgement
                    if (telegramAction !== null) {
                        telegramAction.sendTelegramAnswer(bot1, "BMW_Token_Request", "❌ Token request cancelled.\n\n" +
                            "Please complete the verification and trigger the setup again by updating " + clientIDItem + ".");
                    }
                } else {

                }
            }
            
        } catch (error) {
            logger.error("Exception processing Telegram reply: " + error);
            logger.error("Stack trace: " + error.stack);
        }
    }
});
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Step 2: Request tokens
rules.JSRule({
    name: "BMW Token Setup - Step 2: Token Request",
    description: "Requests access tokens after device code verification",
    triggers: [
        triggers.ItemStateUpdateTrigger(tokenRequestSwitch, "ON")
    ],
    execute: function(event) {
        var logger = log("BMW_Token_Setup_Step2");
              
        try {
            var clientId = items.getItem(clientIDItem).state.toString();
            var deviceCode = items.getItem(deviceCodeItem).state.toString();
            var codeVerifier = items.getItem(codeVerifierItem).state.toString();
            
            // Check if all required values are present
            if (clientId === "NULL" || clientId === "UNDEF" || clientId === "" ||
                deviceCode === "NULL" || deviceCode === "UNDEF" || deviceCode === "" ||
                codeVerifier === "NULL" || codeVerifier === "UNDEF" || codeVerifier === "") {
                logger.error("Required values are missing. Cannot request tokens.");
                
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Request Failed\n\n" +
                        "Required values (Client ID, Device Code, or Code Verifier) are missing.\n\n" +
                        "Please restart the setup process.");
                }
                
                items.getItem(tokenRequestSwitch).postUpdate("OFF");
                return;
            }
            
            logger.info("Requesting tokens from BMW...");
            
            // Prepare HTTP POST request
            var url = "https://customer.bmwgroup.com/gcdm/oauth/token";

            // Build the encoded payload
            var payload = [
                encodeParam("client_id", clientId),
                encodeParam("device_code", deviceCode),
                encodeParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
                encodeParam("code_verifier", codeVerifier),
            ].join("&");
            
            // Create headers as Java HashMap
            var HashMap = Java.type("java.util.HashMap");
            var headers = new HashMap();

            // Execute HTTP request
            var response = actions.HTTP.sendHttpPostRequest(url, "application/x-www-form-urlencoded", payload, headers, 10000);
            
            // Check if response is valid
            if (response === null || response === undefined || response === "") {
                logger.error("No valid response received from BMW.");
                
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Request Failed\n\n" +
                        "No valid response received from BMW during token request.");
                }
                
                items.getItem(tokenRequestSwitch).postUpdate("OFF");
                return;
            }
            
            logger.info("Received response: " + response);
            
            // Parse JSON response
            var jsonResponse = JSON.parse(response);
            
            // Check for errors
            if (jsonResponse.error) {
                var errorMsg = jsonResponse.error_description || jsonResponse.error;
                var errorCode = jsonResponse.error;
                
                logger.error("BMW API Error Code: " + errorCode);
                logger.error("BMW API Error Description: " + errorMsg);
                logger.error("Full JSON Response: " + response);
                
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    var errorMessage = "❌ BMW Token Request Failed\n\n" +
                        "BMW API Error\n\n" +
                        "Error Code: " + escapeTelegram(errorCode) + "\n" +
                        "Description: " + escapeTelegram(errorMsg);
                    
                    // Add specific hints based on error code
                    if (errorCode === "authorization_pending") {
                        errorMessage += "\n\n⚠️ The verification is not yet completed. Please complete the verification at the provided link first.";
                    } else if (errorCode === "invalid_client") {
                        errorMessage += "\n\n⚠️ Client authentication failed. Please check your Client ID.";
                    } else if (errorCode === "invalid_request" || errorCode === "invalid_token") {
                        errorMessage += "\n\n⚠️ The request is invalid or a parameter is missing. Please restart the setup process.";
                    }
                    
                    telegramAction.sendTelegram(bot1, errorMessage);
                }
                
                items.getItem(tokenRequestSwitch).postUpdate("OFF");
                return;
            }
            
            // Check if all required fields are present
            if (!jsonResponse.access_token || !jsonResponse.refresh_token || !jsonResponse.id_token) {
                logger.error("Incomplete token response received.");
                logger.error("Response: " + response);
                
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Request Failed\n\n" +
                        "Incomplete token response received.\n\n" +
                        "Response: " + escapeTelegram(response));
                }
                
                items.getItem(tokenRequestSwitch).postUpdate("OFF");
                return;
            }
            
            // Store tokens in items
            items.getItem(tokenAccessItem).postUpdate(jsonResponse.access_token);
            items.getItem(tokenRefreshItem).postUpdate(jsonResponse.refresh_token);
            items.getItem(tokenIdItem).postUpdate(jsonResponse.id_token);

            // Calculate expiry time of the token minus 5 minutes
            var expireDateTime = Date.now() + (parseInt(jsonResponse.expires_in) * 1000) - 5*60*1000;
            var readableTime = formatDate(expireDateTime);

            // Safe the expiry time into the item
            refreshTokenExpiry.postUpdate(expireDateTime);
            
            logger.info("Tokens successfully received and stored!");
            logger.info("Access Token expires in: " + jsonResponse.expires_in + " seconds");
            
            // Send success message via Telegram
            if (telegramAction !== null) {
                telegramAction.sendTelegram(bot1, "✅ BMW Token Setup Complete!\n\n" +
                    "All tokens have been successfully created and stored.\n\n" +
                    "Access Token expires in: " + jsonResponse.expires_in + " seconds (" + readableTime.toString() + ")\n\n" +
                    "Automatic token refresh will take over every 55 minutes.");
            }
            
            // Reset trigger item
            items.getItem(tokenRequestSwitch).postUpdate("OFF");
            
        } catch (error) {
            logger.error("Exception during token request: " + error);
            logger.error("Error type: " + error.name);
            logger.error("Error message: " + error.message);
            logger.error("Stack trace: " + error.stack);
            
            if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                telegramAction.sendTelegram(bot1, "❌ BMW Token Request Exception\n\n" +
                    "Exception occurred during token request.\n\n" +
                    "Error: " + escapeTelegram(error.toString()) + "\n" +
                    "Type: " + escapeTelegram(error.name) + "\n" +
                    "Message: " + escapeTelegram(error.message) + "\n\n" +
                    "Tokens were NOT requested.");
            }
            
            // Reset trigger item
            items.getItem(tokenRequestSwitch).postUpdate("OFF");
        }
    }
});
  1. Open the vehicle overview and select “BMW CarData”
    https://www.bmw.de/de-de/mybmw/vehicle-overview

  2. Under “Technical access to BMW CarData”, select “Create CarData Client”
    Copy the Client_ID and enable both switches — wait a moment before activating the second one.
    If you receive an error message, close the tab and reload the page.

  3. Paste your Client_ID into the openHAB item carBMW_Client_ID to start the creation of your tokens.

  4. openHAB will send you two Telegram messages to the ID you provided.
    The first message will contain the link to verify your Client_ID and a second will ask if you completed the verification.
    Please open the link and it should directly confirm your Client_ID.

  5. Use the radio button to answer Yes and the process will finally get your tokens.

  6. The tokens will be stored in your items and if you also installed the BMW_ID_Token_Updates.js your MQTT broker thing will be updated with the new ID Token.

2. openHAB MQTT broker and generic thing

Now that you created the Client_ID and tokens, we can create the MQTT broker and generic thing.
I did this through the main UI.

New thing → MQTT binding → MQTT broker

  1. Set your desired UID, i just used bmw (mqtt:broker:bmw)
  2. Set a laben that works for you, i used “BMW MQTT Broker”
  3. Broker hostname = customer.streaming-cardata.bmwgroup.com
  4. Port = 9000
  5. Secure connection = Yes
  6. Confirm hostname = Yes
  7. Protocol = TCP
  8. MQTT Version = 5
  9. Service quality = atleast once (1)
  10. Client-ID = Your Client_ID or empty (openHAB would create a Client_ID for you)
  11. Username = Your gcid / username
  12. Password = Your ID_Token
  13. Discovery = No ← Important!
  14. Save

New thing → MQTT binding → Generic MQTT thing

  1. Set your desired UID
  2. Set your desired label
  3. Create a new channel
    3.1 UID = mileage
    3.2 Label = Odometer
    3.3 MQTT state topic = < Your username>/< Your VIN>
    e.g. 12345678-1234-1234-123456789012/WBA1AB23456CD78901
    3.4 Unit of Measurement = km / mi
    3.5 Incoming Value Transformation = JSONPATH:$.data["vehicle.vehicle.travelledDistance"].value
  4. Link or create an item of your choice to this channel
  5. Done → Save

3. Setup the token update rules

Now we need to setup a rule that will refresh our tokens and input the ID_Token as new password into our MQTT broker thing.

3.1 Items to store your tokens

Before we can refresh the tokens, we need to create items that store our tokens between the refreshes.
I also added a switch to manually trigger a refresh which is mostly for testing and see if things work as intended.
Items:

String carBMW_Token_Access "Access Token [%s]" (gPersist, gBMWTokens)
String carBMW_Token_Refresh "Refresh Token [%s]" (gPersist, gBMWTokens)
String carBMW_Token_ID "ID Token [%s]" (gPersist, gBMWTokens)
String carBMW_Client_ID "Client ID [%s]" (gPersist, gBMWTokens)
String carBMW_Code_Device "Device Code [%s]" (gPersist, gBMWTokens)
String carBMW_Code_User "User Code [%s]" (gPersist, gBMWTokens)
String carBMW_Code_Challenge "Challenge Code [%S]" (gPersist, gBMWTokens)
String carBMW_Code_Verifier "Verifier Code [%S]" (gPersist, gBMWTokens)
Switch carBMW_Token_Refresh_Trigger "Token manuell aktualisieren" (gBMWTokens)
Switch carBMW_Token_Request "Token Request durchführen" (gBMWTokens)
Number carBMW_Expire "Ablaufdatum" (gPersist, gBMWTokens)

I know that it might not be the best idea to store the password as plain text in an item, but i´m currently not aware of better ideas.

3.2 openHAB API Token

As the rule to update the password in the MQTT broker thing uses the Rest API, we need to create an API token first.
Open the Main UI → Login → Click on your user → Scroll down to API-Token → Create new API token
Enter the credentials of your user, choose a name for the token and create it.
Now copy the token and store it inside of the variable ‘apiToken’ in line 8 of ‘BMW_ID_Token_Update.js’

3.3 Refresh rule

I like to work with file based rules and created “BMW_Token_Refresh.js” in automation/js.
This rule will update the tokens 5 minutes before they expire.
You can enter your Telegram bot ID and use the global switches to enable or disable notifications.
I used the success message to make sure the tokens will be refreshed and now only use the error notification.

BMW_Token_Refresh.js
Enter your Telegram Bot ID in line 23 / 24!

/**
 * BMW Token Refresh Rule
 * Automatically updates Access Token, ID Token and Refresh Token
 * WITH SECURITY CHECKS - Tokens are ONLY updated on successful response
 * Last update: 26.10.2025 - 12:20
 */


// Global configuration
var TELEGRAM_NOTIFY_SUCCESS = false; // Send Telegram notification on successful token refresh
var TELEGRAM_NOTIFY_ERROR = true; // Send Telegram notification on errors
var RETRY_DELAY_SECONDS = 30; // Time before doing a retry when the first try fails
var isRetryAttempt = false; // Track if this is a retry

// Item configuration
var refreshTokenExpiryItem = 'carBMW_Expire';
var clientIDItem = 'carBMW_Client_ID';
var tokenAccessItem = 'carBMW_Token_Access';
var tokenRefreshItem = 'carBMW_Token_Refresh';
var tokenIdItem = 'carBMW_Token_ID';
var tokenRefreshTriggerSwitch = 'carBMW_Token_Refresh_Trigger';

// Telegram configuration
var bot1 = 1234; // Enter your telegram bot ID
var tgAddOnId = 'telegram:telegramBot:XYZ'; // Your telegram binding ID

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// please don't change the code from here
//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
var telegramAction = actions.get('telegram', tgAddOnId);
var refreshTokenExpiry = items.getItem(refreshTokenExpiryItem); // Item that stores the expiry time
// Function to escape special characters for Telegram
function escapeTelegram(text) {
    if (text === null || text === undefined) {
        return "";
    }
    text = text.toString();
    if (text.includes('{') || text.includes('[')) {
        return '`' + text + '`';
    }
    return text.replace(/[_*`[\]]/g, '\\$&');
    };

// Function to schedule retry
function scheduleRetry() {
    var logger = log("BMW_Token_Refresh");
    
    logger.warn("Scheduling retry in " + RETRY_DELAY_SECONDS + " seconds...");
    
    try {
        var retryTimer = actions.ScriptExecution.createTimer(
            java.time.ZonedDateTime.now().plusSeconds(RETRY_DELAY_SECONDS),
            function() {
                isRetryAttempt = true;
                performTokenRefresh();
            }
        );
    } catch (error) {
        logger.error("Error scheduling retry: " + error);
    }
}

// Check the expire time of the token
function isStillValid(timestamp) {
    var logger = log("BMW_Token_Refresh_ExpireCheck");
    var currentTime = Date.now();
  
    logger.info('current time: ' + formatDate(currentTime) + ', expiry of token: ' + formatDate(timestamp));
    
    if (currentTime < timestamp) {
        return true;
    } else {
        return false;
    }
}

// Convert EPOCH to DateTime
function formatDate(epochMillis) {
    var date = new Date(epochMillis);

    var day = String(date.getDate()).padStart(2, '0');
    var month = String(date.getMonth() + 1).padStart(2, '0');
    var year = date.getFullYear();
    var hours = String(date.getHours()).padStart(2, '0');
    var minutes = String(date.getMinutes()).padStart(2, '0');
    var seconds = String(date.getSeconds()).padStart(2, '0');

    return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`;
}

// Main refresh function
function performTokenRefresh() {
    var logger = log("BMW_Token_Refresh");
    
    if (isRetryAttempt) {
        logger.info("=== RETRY ATTEMPT - Second try after initial failure ===");
    }
    
    try {
        // Read tokens and Client ID from items
        var refreshToken = items.getItem(tokenRefreshItem).state.toString();
        var clientId = items.getItem(clientIDItem).state.toString();
        
        // Check if tokens are present
        if (refreshToken === "NULL" || refreshToken === "UNDEF" || refreshToken === "" ||
            clientId === "NULL" || clientId === "UNDEF" || clientId === "") {
            logger.warn("Refresh Token or Client ID not set. Please configure initially.");
            
            if (isRetryAttempt && telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                telegramAction.sendTelegram(bot1, "⚠️ BMW Token Refresh Failed (after retry)\n\n" +
                    "Refresh Token or Client ID not set. Please configure initially.");
            }
            
            // Reset retry flag
            isRetryAttempt = false;
            return;
        }
        
        logger.info("Starting token refresh...");
        
        // Prepare HTTP POST request
        var url = "https://customer.bmwgroup.com/gcdm/oauth/token";
        var contentType = "application/x-www-form-urlencoded";
        var payload = "grant_type=refresh_token&refresh_token=" + refreshToken + "&client_id=" + clientId;
        var headers = {};
        var timeout = 10000;
        
        // Execute HTTP request
        var response = actions.HTTP.sendHttpPostRequest(url, contentType, payload, headers, timeout);
        
        // CRITICAL: Check if response is valid
        if (response === null || response === undefined || response === "") {
            logger.error("No valid response received from BMW. Tokens will NOT be updated!");
            
            if (isRetryAttempt) {
                // Second attempt failed - send notification
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Refresh Failed\n\n" +
                        "No valid response received from BMW after retry.\n\n" +
                        "Tokens were NOT updated.\n\n" +
                        "Next automatic attempt in 30 minutes.");
                }
                isRetryAttempt = false;
            } else {
                // First attempt failed - schedule retry
                logger.warn("First attempt failed. Will retry in " + RETRY_DELAY_SECONDS + " seconds.");
                scheduleRetry();
            }
            return;
        }
        
        // Parse JSON response
        var jsonResponse = JSON.parse(response);
        
        // CRITICAL: Check if an error was returned
        if (jsonResponse.error) {
            var errorMsg = jsonResponse.error_description || jsonResponse.error;
            var errorCode = jsonResponse.error;
            
            logger.error("BMW API returned error");
            logger.error("Error Code: " + errorCode);
            logger.error("Error Description: " + errorMsg);
            logger.error("Full JSON Response: " + response);
            
            if (jsonResponse.error === "expired_token") {
                logger.error("====================================================");
                logger.error("REFRESH TOKEN HAS EXPIRED!");
                logger.error("====================================================");
                logger.error("ACTION REQUIRED: Please retrieve new Refresh Token manually from BMW!");
                
                // Always notify on expired token, even on first attempt
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "🚨 BMW Token Refresh - CRITICAL\n\n" +
                        "REFRESH TOKEN HAS EXPIRED!\n\n" +
                        "Error Code: " + escapeTelegram(errorCode) + "\n" +
                        "Description: " + escapeTelegram(errorMsg) + "\n\n" +
                        "⚠️ ACTION REQUIRED:\n" +
                        "Please retrieve new Refresh Token manually from BMW and update " + tokenRefreshItem + "!");
                }
                isRetryAttempt = false;
                return;
            } else {
                // Other BMW API error
                if (isRetryAttempt) {
                    // Second attempt failed - send notification
                    if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                        telegramAction.sendTelegram(bot1, "❌ BMW Token Refresh Failed (after retry)\n\n" +
                            "BMW API Error\n\n" +
                            "Error Code: " + escapeTelegram(errorCode) + "\n" +
                            "Description: " + escapeTelegram(errorMsg) + "\n\n" +
                            "Tokens were NOT updated.\n\n" +
                            "Next automatic attempt in 30 minutes.");
                    }
                    isRetryAttempt = false;
                } else {
                    // First attempt failed - schedule retry
                    logger.warn("First attempt failed with BMW API error. Will retry in " + RETRY_DELAY_SECONDS + " seconds.");
                    scheduleRetry();
                }
                return;
            }
        }
        
        // CRITICAL: Check if all required fields are present
        if (!jsonResponse.access_token || !jsonResponse.refresh_token || !jsonResponse.id_token) {
            logger.error("Incomplete token response received. Tokens will NOT be updated!");
            logger.error("Full Response: " + response);
            
            if (isRetryAttempt) {
                // Second attempt failed - send notification
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Refresh Failed (after retry)\n\n" +
                        "Incomplete token response received.\n\n" +
                        "Tokens were NOT updated.");
                }
                isRetryAttempt = false;
            } else {
                // First attempt failed - schedule retry
                logger.warn("First attempt failed with incomplete response. Will retry in " + RETRY_DELAY_SECONDS + " seconds.");
                scheduleRetry();
            }
            return;
        }
        
        // Check if tokens are not empty or undefined
        if (jsonResponse.access_token === "" || jsonResponse.access_token === null ||
            jsonResponse.refresh_token === "" || jsonResponse.refresh_token === null ||
            jsonResponse.id_token === "" || jsonResponse.id_token === null) {
            logger.error("Received tokens are empty. Tokens will NOT be updated!");
            
            if (isRetryAttempt) {
                // Second attempt failed - send notification
                if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                    telegramAction.sendTelegram(bot1, "❌ BMW Token Refresh Failed (after retry)\n\n" +
                        "Received tokens are empty.\n\n" +
                        "Tokens were NOT updated.");
                }
                isRetryAttempt = false;
            } else {
                // First attempt failed - schedule retry
                logger.warn("First attempt failed with empty tokens. Will retry in " + RETRY_DELAY_SECONDS + " seconds.");
                scheduleRetry();
            }
            return;
        }
        
        // ONLY if all checks are successful: Update tokens
        items.getItem(tokenAccessItem).postUpdate(jsonResponse.access_token);
        items.getItem(tokenRefreshItem).postUpdate(jsonResponse.refresh_token);
        items.getItem(tokenIdItem).postUpdate(jsonResponse.id_token);

        // Calculate expiry time of the tokens minus 5 minutes
        var expireDateTime = Date.now() + (parseInt(jsonResponse.expires_in) * 1000) - 5*60*1000;
        var readableTime = formatDate(expireDateTime);

        // Safe the expiry time into the item
        refreshTokenExpiry.postUpdate(expireDateTime);
        logger.info("Token valid until: " + readableTime.toString());
        
        if (isRetryAttempt) {
            logger.info("Tokens successfully updated on RETRY attempt.");
        } else {
            logger.info("Tokens successfully updated.");
        }
        
        // Send success notification
        if (telegramAction !== null && TELEGRAM_NOTIFY_SUCCESS) {
            var successMsg = "✅ BMW Token Refresh Successful\n\nAll tokens have been updated successfully.";
            if (isRetryAttempt) {
                successMsg = "✅ BMW Token Refresh Successful (on retry)\n\n" +
                    "First attempt failed, but retry was successful.\n\n" +
                    "All tokens have been updated.";
            }
            telegramAction.sendTelegram(bot1, successMsg);
        }
        
        // Reset retry flag
        isRetryAttempt = false;
        
    } catch (error) {
        logger.error("Exception during token refresh: " + error);
        logger.error("Error type: " + error.name);
        logger.error("Error message: " + error.message);
        logger.error("Stack trace: " + error.stack);
        logger.error("Tokens were NOT changed - old tokens remain preserved.");
        
        if (isRetryAttempt) {
            // Second attempt failed - send notification
            if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                telegramAction.sendTelegram(bot1, "❌ BMW Token Refresh Exception (after retry)\n\n" +
                    "Exception occurred during refresh.\n\n" +
                    "Error: " + escapeTelegram(error.toString()) + "\n" +
                    "Type: " + escapeTelegram(error.name) + "\n" +
                    "Message: " + escapeTelegram(error.message) + "\n\n" +
                    "Tokens were NOT changed.\n\n" +
                    "Next automatic attempt in 30 minutes.");
            }
            isRetryAttempt = false;
        } else {
            // First attempt failed - schedule retry
            logger.warn("First attempt failed with exception. Will retry in " + RETRY_DELAY_SECONDS + " seconds.");
            scheduleRetry();
        }
    }
}

// Automatic refresh rule - runs every minute
rules.JSRule({
    name: "BMW Token Refresh",
    description: "Automatically updates BMW tokens 5 minutes before they expire",
    triggers: [
        triggers.GenericCronTrigger("0 * * * * ?")  // Every minute
    ],
    execute: function(event) {
        var logger = log("BMW_Token_Refresh");

        // Import the current expire time
        var refreshTokenExpiryTime = parseInt(refreshTokenExpiry.state);

        if (isNaN(refreshTokenExpiryTime)) {
            logger.warn(refreshTokenExpiryItem + " is not a valid timestamp: " + refreshTokenExpiry.state);
        } else if (isStillValid(refreshTokenExpiryTime)) {
            logger.info("The access token is still valid -> wait");
        } else {
            logger.info("Token needs to be updated! -> Starting update");
            isRetryAttempt = false;
            performTokenRefresh();
        }

    }
});

// Manual refresh via switch
rules.JSRule({
    name: "BMW Token Refresh - Manual",
    description: "Manual token refresh via switch",
    triggers: [
        triggers.ItemCommandTrigger(tokenRefreshTriggerSwitch, "ON")
    ],
    execute: function(event) {
        var logger = log("BMW_Token_Refresh_Manual");
        
        try {
            logger.info("Manual token refresh triggered...");
            
            // Reset retry flag to ensure clean state for manual refresh
            isRetryAttempt = false;
            
            // Call the same function as automatic refresh
            performTokenRefresh();
            
        } catch (error) {
            logger.error("Error triggering manual token refresh: " + error);
            logger.error("Stack trace: " + error.stack);
            
            // Send error notification
            if (telegramAction !== null && TELEGRAM_NOTIFY_ERROR) {
                telegramAction.sendTelegram(bot1, "❌ BMW Manual Token Refresh Exception\n\n" +
                    "Could not trigger manual refresh.\n\n" +
                    "Exception: " + escapeTelegram(error.toString()));
            }
        } finally {
            // Always turn off the trigger switch
            items.getItem(tokenRefreshTriggerSwitch).postUpdate("OFF");
        }
    }
});

3.4 Token update rule

This rule will update the password of your MQTT broker thing once the id_token was refreshed.
"BMW_ID_Token_Update.js in automation/js

`BMW_ID_Token_Update.js`
Please enter your apiToken and MQTT broker thing ID in line 8 and 10!

/**
 * MQTT Broker Password Update Rule
 * Automatically updates the MQTT broker password when the ID token is refreshed
 * Uses REST API with authentication for openHAB 5.x
 * Last update: 26.10.2025 - 12:20
 */

// Global configuration
var apiToken = 'oh.1234567890ABCDEFG'; // Enter your openHAB API token
var bmwTokenItemName = 'carBMW_Token_ID'; // Enter your Item that stores the ID token aka MQTT password
var thingUID = "mqtt:broker:bmw"; // Enter the UID of your MQTT broker thing

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// please don't change the code from here
//------------------------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Function to check if the MQTT broker thing is online
function isThingOffline(thing) {
  var thing = osgi.getService("org.openhab.core.thing.ThingRegistry").get(new org.openhab.core.thing.ThingUID(thing));
  return thing && thing.getStatus().toString() === "ONLINE";
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
rules.JSRule({
    name: "Update BMW MQTT Password",
    description: "Updates BMW MQTT broker password when ID token changes",
    triggers: [
        triggers.ItemStateChangeTrigger(bmwTokenItemName)
    ],
    execute: function(event) {
        var logger = log("BMW_MQTT_Password_Update");
        
        try {
            var newToken = items.getItem(bmwTokenItemName).state.toString();
            
            // Check if token is valid
            if (newToken === "NULL" || newToken === "UNDEF" || newToken === "") {
                logger.warn("ID Token is not valid, skipping MQTT password update");
                return;
            }
            
            logger.debug("Updating MQTT broker password with new ID token...");
                        
            // Prepare headers
            var headers = {
                "Content-Type": "application/json",
                "Accept": "application/json"
            };
            
            // Add authentication if API token is available
            if (apiToken !== null) {
                headers["Authorization"] = "Bearer " + apiToken;
            }
            
            // Get current Thing configuration via REST API
            var getUrl = "http://localhost:8080/rest/things/" + thingUID;
            var thingJson = actions.HTTP.sendHttpGetRequest(getUrl, headers, 5000);
            
            if (thingJson === null || thingJson === "") {
                logger.error("Could not retrieve Thing configuration");
                return;
            }
            
            var thingConfig = JSON.parse(thingJson);
            
            // Update password in configuration
            thingConfig.configuration.password = newToken;
            
            // Send updated configuration back via REST API
            var putUrl = "http://localhost:8080/rest/things/" + thingUID;
            
            var response = actions.HTTP.sendHttpPutRequest(
                putUrl,
                "application/json",
                JSON.stringify(thingConfig),
                headers,
                5000
            );
            
            logger.debug("MQTT broker password successfully updated with new ID token");
            
        } catch (error) {
            logger.error("Error updating MQTT broker password: " + error);
        }
    }
});
//------------------------------------------------------------------------------------------------------------------------------------------------------------
// Optional: Initial setup rule to set password on openHAB startup
rules.JSRule({
    name: "Set BMW MQTT Password",
    description: "Sets BMW MQTT broker password from ID token on system startup",
    triggers: [
        triggers.SystemStartlevelTrigger(100)
    ],
    execute: function(event) {
        var logger = log("BMW_MQTT_Password_Startup");

        if(isThingOffline(thingUID)) {
            logger.info("MQTT Thing online, no update needed.")
            return;
        }
        
        try {
            // Wait a bit for system to be fully ready
            java.lang.Thread.sleep(10000);
            
            var idToken = items.getItem(bmwTokenItemName).state.toString();
            
            if (idToken === "NULL" || idToken === "UNDEF" || idToken === "") {
                logger.warn("ID Token not available on startup");
                return;
            }
                        
            logger.debug("Setting MQTT broker password from stored ID token...");
                        
            // Prepare headers
            var headers = {
                "Content-Type": "application/json",
                "Accept": "application/json"
            };
            
            // Add authentication if API token is available
            if (apiToken !== null) {
                headers["Authorization"] = "Bearer " + apiToken;
            }
            
            // Get current Thing configuration via REST API
            var getUrl = "http://localhost:8080/rest/things/" + thingUID;
            var thingJson = actions.HTTP.sendHttpGetRequest(getUrl, headers, 5000);
            
            if (thingJson === null || thingJson === "") {
                logger.error("Could not retrieve Thing configuration on startup");
                return;
            }
            
            var thingConfig = JSON.parse(thingJson);
            
            // Update password in configuration
            thingConfig.configuration.password = idToken;
            
            // Send updated configuration back via REST API
            var putUrl = "http://localhost:8080/rest/things/" + thingUID;
            
            var response = actions.HTTP.sendHttpPutRequest(
                putUrl,
                "application/json",
                JSON.stringify(thingConfig),
                headers,
                5000
            );
            
            logger.debug("MQTT broker password set successfully on startup");
            
        } catch (error) {
            logger.error("Error setting MQTT broker password on startup: " + error);
        }
    }
});

Test your configuration

Once you´re done, you can check your configuration by sending a command to your BMW through the MyBMW app, e.g. lock the car.
This should trigger a new message and your mileage item should receive the current mileage.

4. Create more channels

Once your test was successful, you can start to add more channels for your datapoints.
To find out which datapoints your car uses, you can create a channel without the input value transformation and link it to a text item.
String carBMW_RAW_Data "Raw data [%s]" (gPersist) {channel="mqtt:topic:bmw:m340:raw-data"}
Open the log viewer, and filter for carBMW_RAW_Data' updated, trigger another update through the MyBMW app and wait for the logs.
Now you can save them as csv, use Excel to dismiss double values and see what´s available for your car.

Changelog

  • 0.1 - 06.10.2025
    Initial concept

  • 0.2 - 07.10.2025
    The periodic refresh failed after a few hours with an exception “error”
    Added a “retry once” strategy and more logging

  • 0.3 - 07.10.2025
    Added a replace to catch _ as character that can´t be sent with Telegram

  • 0.4 - 07.10.2025
    Added better logging and reworked reschedule timer.

  • 0.5 - 08.10.2025
    I switched back to a cronjob and hopefully this will make the rule more reliable

  • 0.6 - 23.10.2025
    Added a header to the sendHttpPostRequest

  • 0.7 - 23.10.2025
    Added the Setup rule to get your tokens with a Telegram flow.

  • 0.8 - 24.10.2025
    Thanks to MartinOpenhabFan i could change the logic for the refresh from every 30 minutes to 5 minutes before the tokens expire.

  • 0.9 - 25.10.2025
    Added variables to configure your personal telegram items.
    Added some more variables to configure your personal items.

  • 0.10 - 26.10.2025
    Added a check to the ID token update and the startup rule only runs if the MQTT broker thing is offline


Contributors


Disclaimer: The rules were created with Claude AI and ChatGPT.

5 Likes

To do list

  • Clean the code and comments
  • Combine more rules into one
2 Likes

Awesome! Patiently waiting for that, since the ‘out of the box’ BMW procedure seems a lot of hassle just for reading out the SOC of my partner’s i3 :wink:

1 Like

I‘m currently in greek and couldn‘t finish it before my holiday.
I‘ll be back next week and post the first version.

Hi Michael, really great that you created the scripts. I follow a slightly different approach based on your examples, I configured everything via Main UI.

I’m storing the expiry timestamp in a variable and run the script each Minute and only if the expire timestamp is less than 5mins away, then I trigger the refresh. It runs since some days reliably.

There is just one issue in your script in the POST request to trigger the token refresh, the method signature has the header map at fourth param and the timeout as fifth. This issue caused an error with each second request. I fixed it by adding a header and now it runs without issues.

Do you see any benefit in running the refresh just before the tokens expire?
My 30 minute refresh now runs reliable and the issues were in the backend of BMW.

I need to check if i updated the post to my latest version. As it‘s working, i‘m not sure if it‘s really wrong or just a different approach :slight_smile:

No, except the issue in the POST request there’s nothing wrong, just a different approach. I just wanted to utilize the expiry time.

Is it written in JS and could you share it?

Sure I’m just busy until the weekend.

1 Like

Thanks for this new connection. I did the steps and got all the details but can’t get it working with MQTTX. I get the bad username or password. I used for Password the id_token what you mentioned but not sure about the username. I tried different options but also the username shown at the CARDATA-STREAM but with the same result. Any suggestions?

Your username is the gcid from the last step.

I have MQTTX working, the broker is online and I created a things file and added the items. I also set the org.openhab.binding.mqtt.generic in trace mode but I don’t receive any data. How to debug this?

Bridge mqtt:broker:1213a7a8b1 [ ] 
{
  Thing topic bmwCar "BMW iX3 Datastream" {
     Channels:
      Type number : lastRemainingRange [
        stateTopic="bba7b916-ebae-43d3-bc23-cc7bd1f18f7c/XBY7X410X0S130998/vehicle/drivetrain/lastRemainingRange",
        transformationPattern="JSONPATH:$.value"
      ]
      Type number : avgElectricRangeConsumption [
        stateTopic="bba7b916-ebae-43d3-bc23-cc7bd1f18f7c/XBY7X410X0S130998/vehicle/drivetrain/avgElectricRangeConsumption",
        transformationPattern="JSONPATH:$.value"
      ]
      Type string : debugAll [
        stateTopic="bba7b916-ebae-43d3-bc23-cc7bd1f18f7c/XBY7X410X0S130998/#"
      ]
      Type string : debugAllState [
         stateTopic="bba7b916-ebae-43d3-bc23-cc7bd1f18f7c/#"
      ]
  }
}

and

Number BMWlastRemainingRange "Actueel bereik [%.0f km]" { channel="mqtt:topic:bmwCar:lastRemainingRange" }
Number BMWAvgElectricRangeConsumption "Gemiddeld verbruik [%.2f kWh/100km]" { channel="mqtt:topic:bmwCar:avgElectricRangeConsumption" }
String BMWRawData "BMW Debug [%s]" { channel="mqtt:topic:bmwCar:debugAll" }
String BMWRawDataState "BMW Debug [%s]" { channel="mqtt:topic:bmwCar:debugAllState" }


You wont receive any updates unless your car is sending new data.

You can force an update through the MyBMW app, just send a command like lock or light to the car.

Yes I know but expect when MQTTX receives date I would also see date in OH.

You can‘t have MQTTX and openHAB broker online at the same time with the same credentials.

New update added with rule to do the initial setup through telegram messages.

Edit: Another update added and a huge shoutout to @MartinOpenhabFan for his input and sharing his approach with me.

1 Like

Many thanks :slight_smile: ! Eager to try this out immediately, I went straight to “1.2 openHAB Telegram” and did steps 1, 2 and 3. I suppose after that i have to run the provided rule with my Telegram Bot ID in line 9, correct? When i do that, openhab log says:

22:43:21.846 [INFO ] [g.openhab.automation.openhab-js.rules] - Adding rule: BMW Token Setup - Step 1: Device Code Request22:43:21.869 [INFO ] [g.openhab.automation.openhab-js.rules] - Adding rule: BMW Token Setup - Handle Telegram Reply22:43:21.879 [WARN ] [odule.handler.ItemStateTriggerHandler] - Item ‘tgReplyId’ needed for rule ‘BMW-Token-Setup—Handle-Telegram-Reply-code_altered_just_for_sure’ is missing. Trigger ‘other_code_altered_just_for_sure’ will not work.22:43:21.893 [INFO ] [g.openhab.automation.openhab-js.rules] - Adding rule: BMW Token Setup - Step 2: Token Request

Nothing is arriving via Telegram. Am I missing something here? I did the Telegram binding setup a long time ago and is otherwise functional for sending OH messages to my phone.

Ah i‘m sorry, you need to input your Telegram itemd for the replyId and lastMessageText.
I‘ll add that info and where to input the items.

Fixed :slight_smile:

You can now configure your personal telegram items in line 13 / 14.

Edit: Added some more variables to configure.

1 Like

Sorry but I’m possibly suffering from Telegram-related confusion :wink:

The rule says:
// Telegram configuration
var bot1 = 1234; // Enter your telegram bot ID
var tgAddOnId = ‘telegram:telegramBot:XYZ’; // Enter your telegram binding ID

I find in my OH Telegram Bot thingy:
Thing UID, i.e:
telegram:telegramBot:a1234bcd5e
Chat Id(s), i.e
123456789

Assuming that by “telegram bot ID” in “var bot1” is meant the Chat ID and the “telegram binding ID” is indeed the Thing UID, i think i have configured that correctly now. The error in OH log is gone, but I’m still not receiving any Telegram messages.

22:09:08.182 [INFO ] [g.openhab.automation.openhab-js.rules] - Adding rule: BMW Token Setup - Step 1: Device Code Request
22:09:08.210 [INFO ] [g.openhab.automation.openhab-js.rules] - Adding rule: BMW Token Setup - Handle Telegram Reply
22:09:08.234 [INFO ] [g.openhab.automation.openhab-js.rules] - Adding rule: BMW Token Setup - Step 2: Token Request