From e1d150b40dc0f6c9485b8f01ec68ab77b9ab1d6c Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Tue, 10 Feb 2026 17:19:28 +0000 Subject: [PATCH 01/18] Minor changes to return OK on error --- .idea/.gitignore | 10 ++++++++++ .idea/TeslaBleHttpProxy.iml | 9 +++++++++ .idea/go.imports.xml | 11 +++++++++++ .idea/golinter.xml | 7 +++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ internal/api/handlers/tesla.go | 4 ++-- 7 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/TeslaBleHttpProxy.iml create mode 100644 .idea/go.imports.xml create mode 100644 .idea/golinter.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/TeslaBleHttpProxy.iml b/.idea/TeslaBleHttpProxy.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/TeslaBleHttpProxy.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 0000000..d7202f0 --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/.idea/golinter.xml b/.idea/golinter.xml new file mode 100644 index 0000000..1ccf3ec --- /dev/null +++ b/.idea/golinter.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..08447c9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index ac91575..d835b25 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -269,7 +269,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { } responseJson, err := json.Marshal(combinedResponse) if err != nil { - response.Result = false + response.Result = true response.Reason = apiResponse.Error return } @@ -277,7 +277,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { response.Reason = "The request was partially processed from cache. Some data may be stale." response.Response = responseJson } else { - response.Result = false + response.Result = true response.Reason = apiResponse.Error } } From f2303ccf5aed374a23bd4d41446b332620b13812 Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Tue, 10 Feb 2026 17:53:29 +0000 Subject: [PATCH 02/18] Minor change testes --- internal/api/handlers/tesla.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index d835b25..c6a7c3f 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -176,11 +176,13 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { } else { // Cache expired for this endpoint logging.Debug("VehicleData endpoint cache expired", "VIN", vin, "Endpoint", endpoint, "Age", age) + cachedData[endpoint] = cachedEntry.data missingEndpoints = append(missingEndpoints, endpoint) } } else { // Cache miss for this endpoint logging.Debug("VehicleData endpoint cache miss", "VIN", vin, "Endpoint", endpoint) + cachedData[endpoint] = cachedEntry.data missingEndpoints = append(missingEndpoints, endpoint) } } From cbe49a6bb498c9ef8ef0e60254aaa4d10ec92886 Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Tue, 10 Feb 2026 18:00:04 +0000 Subject: [PATCH 03/18] Correcao de erro nao existe cachedata --- internal/api/handlers/tesla.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index c6a7c3f..c938680 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -182,7 +182,6 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { } else { // Cache miss for this endpoint logging.Debug("VehicleData endpoint cache miss", "VIN", vin, "Endpoint", endpoint) - cachedData[endpoint] = cachedEntry.data missingEndpoints = append(missingEndpoints, endpoint) } } From 4b9e06099d5f53606ea20bcd5c6aa6144b73163a Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Wed, 11 Feb 2026 09:39:27 +0000 Subject: [PATCH 04/18] Vehicle out of range tries to wake it the first time it detects the vehicle again. Testing --- internal/api/handlers/tesla.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index c938680..fdeab69 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -31,6 +31,10 @@ var ( vehicleDataCacheMux sync.RWMutex ) +var ( + vehicleOutOfRange bool +) + func commonDefer(w http.ResponseWriter, response *models.Response) { var ret models.Ret ret.Response = *response @@ -219,6 +223,15 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { wg.Wait() + if !apiResponse.Result && strings.Contains(apiResponse.Error, "not in range") { + vehicleOutOfRange = true + } else { + if vehicleOutOfRange { + control.BleControlInstance.PushCommand(command, vin, map[string]interface{}{"endpoints": endpoints}, &apiResponse, true) + wg.Wait() + } + } + if apiResponse.Result { // Parse the BLE response to extract individual endpoint data var fetchedData map[string]json.RawMessage @@ -257,6 +270,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { return } + vehicleOutOfRange = false response.Result = true response.Reason = "The request was successfully processed." response.Response = responseJson @@ -270,7 +284,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { } responseJson, err := json.Marshal(combinedResponse) if err != nil { - response.Result = true + response.Result = false response.Reason = apiResponse.Error return } @@ -278,7 +292,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { response.Reason = "The request was partially processed from cache. Some data may be stale." response.Response = responseJson } else { - response.Result = true + response.Result = false response.Reason = apiResponse.Error } } From de7133fbfc8945b5102da610ae5ccdaa9f58c6b0 Mon Sep 17 00:00:00 2001 From: JKSProds <39013522+JKSProds@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:13:43 +0000 Subject: [PATCH 05/18] Add note about forked version for EVCC compatibility This program is a fork of the main TeslaBleHttpProxy with some changes so it works correctly with EVCC. --- README.md | 251 +----------------------------------------------------- 1 file changed, 1 insertion(+), 250 deletions(-) diff --git a/README.md b/README.md index 524e09c..b122ad3 100644 --- a/README.md +++ b/README.md @@ -2,253 +2,4 @@ TeslaBleHttpProxy is a program written in Go that receives HTTP requests and forwards them via Bluetooth to a Tesla vehicle. The program can, for example, be easily used together with [evcc](https://github.com/evcc-io/evcc). -The program stores the received requests in a queue and processes them one by one. This ensures that only one Bluetooth connection to the vehicle is established at a time. - -## Table of Contents - -- [How to install](#how-to-install) - - [Docker compose](#docker-compose) - - [Build yourself](#build-yourself) -- [Generate key for vehicle](#generate-key-for-vehicle) -- [Setup EVCC](#setup-evcc) -- [API](#api) - - [Vehicle Commands](#vehicle-commands) - - [Vehicle Data](#vehicle-data) - - [Body Controller State](#body-controller-state) - - [Version of Proxy](#version-of-proxy) -- [Troubleshooting](#troubleshooting) - -## How to install - -You can either compile and use the Go program yourself or install it in a Docker container. ([detailed instruction](docs/installation.md)) - -### Docker compose - -Below you will find the necessary contents for your `docker-compose.yml`: - -``` -services: - tesla-ble-http-proxy: - image: wimaha/tesla-ble-http-proxy - container_name: tesla-ble-http-proxy - volumes: - - ~/TeslaBleHttpProxy/key:/key - - /var/run/dbus:/var/run/dbus - restart: always - privileged: true - network_mode: host - cap_add: - - NET_ADMIN - - SYS_ADMIN -``` - -Please remember to create an empty folder where the keys can be stored later. In this example, it is `~/TeslaBleHttpProxy/key`. - -Pull and start TeslaBleHttpProxy with `docker compose up -d`. - -Note that you can optionally set environment variables to override the default behavior. See [environment variables](docs/environment_variables.md) for more information. - -**Key Security:** Private keys are protected by UNIX file permissions (0600 - owner read/write only). Ensure the key directory has proper permissions and is not accessible to unauthorized users. - -### Build yourself - -Download the code and save it in a folder named 'TeslaBleHttpProxy'. From there, you can easily compile the program. - -``` -go build . -./TeslaBleHttpProxy -``` - -Please remember to create an empty folder called `key` where the keys can be stored later. - -Note that you can optionally set environment variables to override the default behavior. See [environment variables](docs/environment_variables.md) for more information. - -## Generate key for vehicle - -*(Here, the simple, automatic method is described. Besides the automatic method, you can also generate the keys [manually](docs/manually_gen_key.md).)* - -**Security Recommendation:** We recommend using the **Charging Manager** role for security. It provides limited access suitable for charging management and works perfectly with [evcc tesla-ble template](https://docs.evcc.io/docs/devices/vehicles#tesla-ble). The Charging Manager role can: -- Read vehicle data -- Authorize charging-related commands: `wake`, `charge_start`, `charge_stop`, `set_charging_amps` - -The **Owner** role provides full access to all vehicle functions (unlock, start, etc.) and should only be used if you need non-charging functions. - -To generate the required keys browse to `http://YOUR_IP:8080/dashboard`. In the dashboard you will see that the keys are missing: - -Picture of the Dashboard with missing keys. - -Please click on `Generate` for the **Charging Manager** role (recommended for security). The keys will be automatically generated and saved. The Charging Manager key will be set as active by default. - -Picture of the Dashboard with success message and keys.
-Picture of the Dashboard with success message and keys. - -After that please enter your VIN under `Setup Vehicle`. Before you proceed make sure your vehicle is awake! So you have to manually wake the vehicle before you send the key to the vehicle. - -Picture of Setup Vehicle Part of the Dashboard. - -The key is now sent to the vehicle. To complete the process, confirm by tapping your NFC card on the center console. (Note: There will be no message on the Tesla screen before confirmation with the NFC card.) - -Picture of success message sent add-key request. - -You can now close the dashboard and use the proxy. 🙂 - -## Setup EVCC - -You can use the following configuration in evcc (recommended): - -``` -vehicles: - - name: tesla - type: template - template: tesla-ble - title: Your Tesla (optional) - capacity: 60 # Akkukapazität in kWh (optional) - vin: VIN # Erforderlich für BLE-Verbindung - url: IP # URL des Tesla BLE HTTP Proxy - port: 8080 # Port des Tesla BLE HTTP Proxy (optional) -``` - -If you want to use this proxy only for commands, and not for vehicle data, you can use the following configuration. The vehicle data is then fetched via the Tesla API by evcc. - -``` -- name: model3 - type: template - template: tesla - title: Tesla - icon: car - commandProxy: http://YOUR_IP:8080 - accessToken: YOUR_ACCESS_TOKEN - refreshToken: YOUR_REFRSH_TOKEN - capacity: 60 - vin: YOUR_VIN -``` - -(Hint for multiple vehicle support: https://github.com/wimaha/TeslaBleHttpProxy/issues/40) - -## API - -### Vehicle Commands - -The program uses the same interfaces as the Tesla [Fleet API](https://developer.tesla.com/docs/fleet-api#vehicle-commands). Currently, the following requests are supported: - -- wake_up -- charge_start -- charge_stop -- set_charging_amps -- set_charge_limit -- auto_conditioning_start -- auto_conditioning_stop -- charge_port_door_open -- charge_port_door_close -- flash_lights -- honk_horn -- door_lock -- door_unlock -- set_sentry_mode - -By default, the program will return immediately after sending the command to the vehicle. If you want to wait for the command to complete, you can set the `wait` parameter to `true`. - -**Wake Up Behavior:** Commands **automatically wake up** the vehicle if it is asleep. You don't need to manually wake the vehicle or use any parameters - the proxy handles this automatically to ensure commands execute successfully. - -#### Example Request - -*(All requests with method POST.)* - -Start charging: -`http://localhost:8080/api/1/vehicles/{VIN}/command/charge_start` - -Start charging and wait for the command to complete: -`http://localhost:8080/api/1/vehicles/{VIN}/command/charge_start?wait=true` - -Stop charging: -`http://localhost:8080/api/1/vehicles/{VIN}/command/charge_stop` - -Set charging amps to 5A: -`http://localhost:8080/api/1/vehicles/{VIN}/command/set_charging_amps` with body `{"charging_amps": "5"}` - -Explicitly wake up the vehicle: -`http://localhost:8080/api/1/vehicles/{VIN}/command/wake_up` - -### Vehicle Data - -The vehicle data is fetched from the vehicle and returned in the response in the same format as the [Fleet API](https://developer.tesla.com/docs/fleet-api/endpoints/vehicle-endpoints#vehicle-data). Since a ble connection has to be established to fetch the data, it takes a few seconds before the data is returned. - -**Caching:** VehicleData responses are cached in memory for faster subsequent requests. Each endpoint (e.g., `charge_state`, `climate_state`) is cached separately per VIN. The cache time can be configured via the `vehicleDataCacheTime` environment variable (default: 30 seconds). If all requested endpoints are cached and valid, the response is returned immediately without establishing a BLE connection. - -**Wake Up Behavior:** By default, the car is **not** automatically woken up before fetching vehicle data. This allows for efficient data retrieval when the vehicle is already awake. If your vehicle is asleep and you need to wake it up first, you can use the `wakeup=true` parameter. The proxy uses intelligent caching to minimize unnecessary wakeup calls - if the vehicle was confirmed awake within the last 9 minutes, the sleep status check is skipped. - -#### Example Request - -*(All requests with method GET.)* - -Get vehicle data: -`http://localhost:8080/api/1/vehicles/{VIN}/vehicle_data` - -Get vehicle data with automatic wakeup: -`http://localhost:8080/api/1/vehicles/{VIN}/vehicle_data?wakeup=true` - -Currently you will receive the following data: - -- charge_state -- climate_state - -If you want to receive specific data, you can add the endpoints to the request. For example: - -`http://localhost:8080/api/1/vehicles/{VIN}/vehicle_data?endpoints=charge_state` - -Get specific data with automatic wakeup: -`http://localhost:8080/api/1/vehicles/{VIN}/vehicle_data?endpoints=charge_state&wakeup=true` - -This is recommended if you want to receive data frequently, since it will reduce the time it takes to receive the data. - -### Body Controller State - -The body controller state is fetched from the vehicle and returnes the state of the body controller. The request does not wake up the vehicle. The following information is returned: - -- `vehicleLockState` - - `VEHICLELOCKSTATE_UNLOCKED` - - `VEHICLELOCKSTATE_LOCKED` - - `VEHICLELOCKSTATE_INTERNAL_LOCKED` - - `VEHICLELOCKSTATE_SELECTIVE_UNLOCKED` -- `vehicleSleepStatus` - - `VEHICLE_SLEEP_STATUS_UNKNOWN` - - `VEHICLE_SLEEP_STATUS_AWAKE` - - `VEHICLE_SLEEP_STATUS_ASLEEP` -- `userPresence` - - `VEHICLE_USER_PRESENCE_UNKNOWN` - - `VEHICLE_USER_PRESENCE_NOT_PRESENT` - - `VEHICLE_USER_PRESENCE_PRESENT` - -#### Request - -*(All requests with method GET.)* - -Get body controller state: -`http://localhost:8080/api/1/vehicles/{VIN}/body_controller_state` - -### Version of Proxy - -Get version of proxy: -`http://localhost:8080/api/proxy/1/version` - -The response will contain the version of the proxy. - -## Troubleshooting - -### Vehicle Requirements - -TeslaBleHttpProxy requires your Tesla vehicle to support **Phone Key** functionality, as it relies on Bluetooth Low Energy (BLE) for communication. Most Tesla models from 2021 onward support Phone Key, but some older models (e.g., Model X 2015–2020) may not. Please verify Phone Key support in your Tesla app or consult Tesla's [Vehicle Keys Support Page](https://www.tesla.com/support/tesla-vehicle-keys) before setting up the proxy. - -### Connection Timeouts - -Due to BLE's power-saving design, Tesla vehicles may terminate connections after ~30 seconds, causing "connection timeout" logs. This is normal, and the proxy reconnects automatically, ensuring EVCC or other integrations work without issues. Keep the proxy device within ~5-10 meters of the vehicle for reliable connections. - -### BLE Device Limit (Maximum 3 Devices) - -**Problem:** Intermittent connection failures, undefined loss of connections, or unreliable behavior with evcc. - -**Solution:** Tesla vehicles only accept a **maximum of 3 BLE keys/devices simultaneously**. If you exceed this limit (e.g., multiple phones, smartwatches, and the proxy), you'll experience connection issues. - -**Fix:** Close the Tesla app on mobile phones when not needed, especially if you're also wearing a smartwatch with the Tesla app. The proxy counts as one device, so limit other active BLE connections accordingly. - -This is a Tesla vehicle constraint, not a limitation of TeslaBleHttpProxy. +This program is a fork of the main TeslaBleHttpProxy with some changes so it works correctly with EVCC. From 331b9d89b1345ef5d32931507ada8c35cdeaa50b Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Wed, 11 Feb 2026 17:32:07 +0000 Subject: [PATCH 06/18] Added logs. --- internal/api/handlers/tesla.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index fdeab69..58f17dc 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -31,9 +31,7 @@ var ( vehicleDataCacheMux sync.RWMutex ) -var ( - vehicleOutOfRange bool -) +var vehicleOutOfRange = true func commonDefer(w http.ResponseWriter, response *models.Response) { var ret models.Ret @@ -224,9 +222,13 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { wg.Wait() if !apiResponse.Result && strings.Contains(apiResponse.Error, "not in range") { + logging.Debug("Vehicle out of range. Setting bool vehicleOutOfRange", "VIN", vin) + fmt.Printf("Vehicle out of range. Setting bool vehicleOutOfRange") vehicleOutOfRange = true } else { if vehicleOutOfRange { + logging.Debug("Vehicle was out of range. Re running command with autoWakeup!", "VIN", vin) + fmt.Printf("Vehicle was out of range. Re running command with autoWakeup!") control.BleControlInstance.PushCommand(command, vin, map[string]interface{}{"endpoints": endpoints}, &apiResponse, true) wg.Wait() } From 346051a9eb56a8a4f55bb59f616fdb7abef193f4 Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Mon, 16 Feb 2026 18:26:26 +0000 Subject: [PATCH 07/18] Change timeout to 2 Added CS charge state default if not in range to Disconnected --- config/config.go | 2 +- internal/api/handlers/tesla.go | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 1186f44..82642a0 100644 --- a/config/config.go +++ b/config/config.go @@ -125,7 +125,7 @@ func LoadConfig() *Config { scanTimeout := os.Getenv("scanTimeout") if scanTimeout == "" { - scanTimeout = "5" // default value + scanTimeout = "2" // default value } scanTimeoutInt, err := strconv.Atoi(scanTimeout) if err != nil { diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index 58f17dc..a9cba4b 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -278,22 +278,38 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { response.Response = responseJson } else { // BLE fetch failed - try to serve from cache if available + var cs models.ChargeState + if len(cachedData) > 0 { logging.Debug("BLE fetch failed, serving partial data from cache", "VIN", vin, "CachedEndpoints", len(cachedData)) combinedResponse := make(map[string]json.RawMessage) for endpoint, data := range cachedData { combinedResponse[endpoint] = data + if err := json.Unmarshal(data, &cs); err != nil { + cs = models.ChargeState{} + } + if vehicleOutOfRange { + cs.ChargingState = "Disconnected" + } } - responseJson, err := json.Marshal(combinedResponse) + + responseJson, err := json.Marshal(cs) if err != nil { response.Result = false response.Reason = apiResponse.Error + return } + response.Result = true response.Reason = "The request was partially processed from cache. Some data may be stale." response.Response = responseJson } else { + cs.ChargingState = "Disconnected" + responseJson, err := json.Marshal(cs) + if err != nil { + } + response.Response = responseJson response.Result = false response.Reason = apiResponse.Error } From 439024504df2131f72f909b01536a04913202b26 Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Mon, 16 Feb 2026 18:48:38 +0000 Subject: [PATCH 08/18] Minor change. Removed else. Try in next try --- internal/api/handlers/tesla.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index a9cba4b..3ead991 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -216,7 +216,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { apiResponse.Ctx = r.Context() wg.Add(1) - autoWakeup := r.URL.Query().Get("wakeup") == "true" + autoWakeup := r.URL.Query().Get("wakeup") == "true" || vehicleOutOfRange control.BleControlInstance.PushCommand(command, vin, map[string]interface{}{"endpoints": endpoints}, &apiResponse, autoWakeup) wg.Wait() @@ -225,13 +225,6 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { logging.Debug("Vehicle out of range. Setting bool vehicleOutOfRange", "VIN", vin) fmt.Printf("Vehicle out of range. Setting bool vehicleOutOfRange") vehicleOutOfRange = true - } else { - if vehicleOutOfRange { - logging.Debug("Vehicle was out of range. Re running command with autoWakeup!", "VIN", vin) - fmt.Printf("Vehicle was out of range. Re running command with autoWakeup!") - control.BleControlInstance.PushCommand(command, vin, map[string]interface{}{"endpoints": endpoints}, &apiResponse, true) - wg.Wait() - } } if apiResponse.Result { From e32b86b47a16716037daf5474f304379779e0494 Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Mon, 16 Feb 2026 18:54:41 +0000 Subject: [PATCH 09/18] Added check to not change var if it is already changed --- internal/api/handlers/tesla.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index 3ead991..b2cbcf1 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -221,7 +221,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { wg.Wait() - if !apiResponse.Result && strings.Contains(apiResponse.Error, "not in range") { + if !apiResponse.Result && strings.Contains(apiResponse.Error, "not in range") && !vehicleOutOfRange { logging.Debug("Vehicle out of range. Setting bool vehicleOutOfRange", "VIN", vin) fmt.Printf("Vehicle out of range. Setting bool vehicleOutOfRange") vehicleOutOfRange = true From a48de182f76321e28190e75711e100a8272d3e23 Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Mon, 16 Feb 2026 19:11:00 +0000 Subject: [PATCH 10/18] Get usable battery level. Closer to actual battery level --- internal/api/models/statesConverter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/models/statesConverter.go b/internal/api/models/statesConverter.go index c93e280..5745c0e 100644 --- a/internal/api/models/statesConverter.go +++ b/internal/api/models/statesConverter.go @@ -25,7 +25,7 @@ func ChargeStateFromBle(VehicleData *carserver.VehicleData) ChargeState { BatteryRange: VehicleData.ChargeState.GetBatteryRange(), EstBatteryRange: VehicleData.ChargeState.GetEstBatteryRange(), IdealBatteryRange: VehicleData.ChargeState.GetIdealBatteryRange(), - BatteryLevel: VehicleData.ChargeState.GetBatteryLevel(), + BatteryLevel: VehicleData.ChargeState.GetUsableBatteryLevel(), //VehicleData.ChargeState.GetBatteryLevel(), UsableBatteryLevel: VehicleData.ChargeState.GetUsableBatteryLevel(), ChargeEnergyAdded: VehicleData.ChargeState.GetChargeEnergyAdded(), ChargeMilesAddedRated: VehicleData.ChargeState.GetChargeMilesAddedRated(), From 84854d0b6679746e06a23103312bd8be6afa8501 Mon Sep 17 00:00:00 2001 From: JKSProds <39013522+JKSProds@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:51:54 +0000 Subject: [PATCH 11/18] Change FastChargerType, ConnChargeCable, and FastChargerBrand to pointers --- internal/api/models/states.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/models/states.go b/internal/api/models/states.go index 881e637..7fa735f 100644 --- a/internal/api/models/states.go +++ b/internal/api/models/states.go @@ -12,7 +12,7 @@ type ChargeState struct { NotEnoughPowerToHeat bool `json:"not_enough_power_to_heat"` // MaxRangeChargeCounter int32 `json:"max_range_charge_counter"` // FastChargerPresent bool `json:"fast_charger_present"` // - FastChargerType string `json:"fast_charger_type"` // + FastChargerType *string `json:"fast_charger_type,omitempty"` // BatteryRange float32 `json:"battery_range"` // EstBatteryRange float32 `json:"est_battery_range"` // IdealBatteryRange float32 `json:"ideal_battery_range"` // @@ -51,8 +51,8 @@ type ChargeState struct { ManagedChargingStartTime interface{} `json:"managed_charging_start_time"` // ChargePortcoldWeatherMode bool `json:"charge_port_cold_weather_mode"` // ChargePortColor string `json:"charge_port_color"` // - ConnChargeCable string `json:"conn_charge_cable"` // - FastChargerBrand string `json:"fast_charger_brand"` // + ConnChargeCable *string `json:"conn_charge_cable,omitempty"` // + FastChargerBrand *string `json:"fast_charger_brand,omitempty"` // MinutesToFullCharge int32 `json:"minutes_to_full_charge"` // } From a8172010a8e7ea22d9fb1d8d045b7899e935397a Mon Sep 17 00:00:00 2001 From: JKSProds <39013522+JKSProds@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:00:33 +0000 Subject: [PATCH 12/18] Update charger type and brand fields to non-pointer Changed FastChargerType, ConnChargeCable, and FastChargerBrand from pointers to strings. --- internal/api/models/states.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/models/states.go b/internal/api/models/states.go index 7fa735f..18caf94 100644 --- a/internal/api/models/states.go +++ b/internal/api/models/states.go @@ -12,7 +12,7 @@ type ChargeState struct { NotEnoughPowerToHeat bool `json:"not_enough_power_to_heat"` // MaxRangeChargeCounter int32 `json:"max_range_charge_counter"` // FastChargerPresent bool `json:"fast_charger_present"` // - FastChargerType *string `json:"fast_charger_type,omitempty"` // + FastChargerType string `json:"fast_charger_type,omitempty"` // BatteryRange float32 `json:"battery_range"` // EstBatteryRange float32 `json:"est_battery_range"` // IdealBatteryRange float32 `json:"ideal_battery_range"` // @@ -51,8 +51,8 @@ type ChargeState struct { ManagedChargingStartTime interface{} `json:"managed_charging_start_time"` // ChargePortcoldWeatherMode bool `json:"charge_port_cold_weather_mode"` // ChargePortColor string `json:"charge_port_color"` // - ConnChargeCable *string `json:"conn_charge_cable,omitempty"` // - FastChargerBrand *string `json:"fast_charger_brand,omitempty"` // + ConnChargeCable string `json:"conn_charge_cable,omitempty"` // + FastChargerBrand string `json:"fast_charger_brand,omitempty"` // MinutesToFullCharge int32 `json:"minutes_to_full_charge"` // } From 15626997f95c01a394473fe47ff91258b7985bc6 Mon Sep 17 00:00:00 2001 From: JKSProds <39013522+JKSProds@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:01:50 +0000 Subject: [PATCH 13/18] Replace nil string with null in JSON response --- internal/api/handlers/tesla.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index b2cbcf1..694f314 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -294,15 +294,19 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { return } + clean := bytes.ReplaceAll(responseJson, []byte(`""`), []byte(`null`)) + response.Response = clean + response.Result = true response.Reason = "The request was partially processed from cache. Some data may be stale." - response.Response = responseJson } else { cs.ChargingState = "Disconnected" responseJson, err := json.Marshal(cs) if err != nil { } - response.Response = responseJson + clean := bytes.ReplaceAll(responseJson, []byte(`""`), []byte(`null`)) + response.Response = clean + response.Result = false response.Reason = apiResponse.Error } From 57da54924379b7bd08c8ab8f89b50a21fad41477 Mon Sep 17 00:00:00 2001 From: JKSProds <39013522+JKSProds@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:06:30 +0000 Subject: [PATCH 14/18] Add bytes package import to tesla.go --- internal/api/handlers/tesla.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index 694f314..5e2d5f0 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -9,6 +9,7 @@ import ( "strings" "sync" "time" + "bytes" "github.com/gorilla/mux" "github.com/wimaha/TeslaBleHttpProxy/config" From 4d394bcd40a8900f6d6bbfffb199e630473db68a Mon Sep 17 00:00:00 2001 From: JKSProds <39013522+JKSProds@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:12:23 +0000 Subject: [PATCH 15/18] Fix nil representation in Tesla API response Replace '<nil>' with 'null' in response JSON. --- internal/api/handlers/tesla.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index 5e2d5f0..754efb0 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -269,7 +269,9 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { vehicleOutOfRange = false response.Result = true response.Reason = "The request was successfully processed." - response.Response = responseJson + + clean := bytes.ReplaceAll(responseJson, []byte(`""`), []byte(`null`)) + response.Response = clean } else { // BLE fetch failed - try to serve from cache if available var cs models.ChargeState From 09084c49e1dccd2612cc3fa7803fea297f8f4a6c Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Tue, 17 Feb 2026 00:24:07 +0000 Subject: [PATCH 16/18] Bug fix nil --- internal/api/models/states.go | 6 +++--- internal/api/models/statesConverter.go | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/api/models/states.go b/internal/api/models/states.go index 18caf94..7fa735f 100644 --- a/internal/api/models/states.go +++ b/internal/api/models/states.go @@ -12,7 +12,7 @@ type ChargeState struct { NotEnoughPowerToHeat bool `json:"not_enough_power_to_heat"` // MaxRangeChargeCounter int32 `json:"max_range_charge_counter"` // FastChargerPresent bool `json:"fast_charger_present"` // - FastChargerType string `json:"fast_charger_type,omitempty"` // + FastChargerType *string `json:"fast_charger_type,omitempty"` // BatteryRange float32 `json:"battery_range"` // EstBatteryRange float32 `json:"est_battery_range"` // IdealBatteryRange float32 `json:"ideal_battery_range"` // @@ -51,8 +51,8 @@ type ChargeState struct { ManagedChargingStartTime interface{} `json:"managed_charging_start_time"` // ChargePortcoldWeatherMode bool `json:"charge_port_cold_weather_mode"` // ChargePortColor string `json:"charge_port_color"` // - ConnChargeCable string `json:"conn_charge_cable,omitempty"` // - FastChargerBrand string `json:"fast_charger_brand,omitempty"` // + ConnChargeCable *string `json:"conn_charge_cable,omitempty"` // + FastChargerBrand *string `json:"fast_charger_brand,omitempty"` // MinutesToFullCharge int32 `json:"minutes_to_full_charge"` // } diff --git a/internal/api/models/statesConverter.go b/internal/api/models/statesConverter.go index 5745c0e..e0e077d 100644 --- a/internal/api/models/statesConverter.go +++ b/internal/api/models/statesConverter.go @@ -11,6 +11,8 @@ func flatten(s string) string { return strings.ReplaceAll(s, ":{}", "") } +func strPtr(s string) *string { return &s } + func ChargeStateFromBle(VehicleData *carserver.VehicleData) ChargeState { return ChargeState{ Timestamp: VehicleData.ChargeState.GetTimestamp().AsTime().Unix(), @@ -21,7 +23,7 @@ func ChargeStateFromBle(VehicleData *carserver.VehicleData) ChargeState { ChargeLimitSocMax: VehicleData.ChargeState.GetChargeLimitSocMax(), MaxRangeChargeCounter: VehicleData.ChargeState.GetMaxRangeChargeCounter(), FastChargerPresent: VehicleData.ChargeState.GetFastChargerPresent(), - FastChargerType: flatten(VehicleData.ChargeState.GetFastChargerType().String()), + FastChargerType: strPtr(flatten(VehicleData.ChargeState.GetFastChargerType().String())), BatteryRange: VehicleData.ChargeState.GetBatteryRange(), EstBatteryRange: VehicleData.ChargeState.GetEstBatteryRange(), IdealBatteryRange: VehicleData.ChargeState.GetIdealBatteryRange(), @@ -59,8 +61,8 @@ func ChargeStateFromBle(VehicleData *carserver.VehicleData) ChargeState { ManagedChargingStartTime: VehicleData.ChargeState.GetManagedChargingStartTime(), ChargePortcoldWeatherMode: VehicleData.ChargeState.GetChargePortColdWeatherMode(), ChargePortColor: flatten(VehicleData.ChargeState.GetChargePortColor().String()), - ConnChargeCable: flatten(VehicleData.ChargeState.GetConnChargeCable().String()), - FastChargerBrand: flatten(VehicleData.ChargeState.GetFastChargerBrand().String()), + ConnChargeCable: strPtr(flatten(VehicleData.ChargeState.GetConnChargeCable().String())), + FastChargerBrand: strPtr(flatten(VehicleData.ChargeState.GetFastChargerBrand().String())), MinutesToFullCharge: VehicleData.ChargeState.GetMinutesToFullCharge(), } } From 599a72b517c01a8332b7119fb879e5fcfa40239e Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Tue, 17 Feb 2026 00:25:06 +0000 Subject: [PATCH 17/18] Rebase --- internal/api/handlers/tesla.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index 754efb0..e81e0bd 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -9,7 +9,6 @@ import ( "strings" "sync" "time" - "bytes" "github.com/gorilla/mux" "github.com/wimaha/TeslaBleHttpProxy/config" @@ -269,9 +268,7 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { vehicleOutOfRange = false response.Result = true response.Reason = "The request was successfully processed." - - clean := bytes.ReplaceAll(responseJson, []byte(`""`), []byte(`null`)) - response.Response = clean + response.Response = responseJson } else { // BLE fetch failed - try to serve from cache if available var cs models.ChargeState @@ -297,21 +294,17 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { return } - clean := bytes.ReplaceAll(responseJson, []byte(`""`), []byte(`null`)) - response.Response = clean - response.Result = true response.Reason = "The request was partially processed from cache. Some data may be stale." + response.Response = responseJson } else { cs.ChargingState = "Disconnected" responseJson, err := json.Marshal(cs) if err != nil { } - clean := bytes.ReplaceAll(responseJson, []byte(`""`), []byte(`null`)) - response.Response = clean - response.Result = false response.Reason = apiResponse.Error + response.Response = responseJson } } } From cfdc531e9273212049abe1d5e0994c0dde8ae7cc Mon Sep 17 00:00:00 2001 From: "JKSProds (Mac)" Date: Fri, 20 Feb 2026 21:26:15 +0000 Subject: [PATCH 18/18] Removido disconnected on outofrange --- internal/api/handlers/tesla.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/internal/api/handlers/tesla.go b/internal/api/handlers/tesla.go index e81e0bd..3d4e4cf 100644 --- a/internal/api/handlers/tesla.go +++ b/internal/api/handlers/tesla.go @@ -271,22 +271,14 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { response.Response = responseJson } else { // BLE fetch failed - try to serve from cache if available - var cs models.ChargeState - if len(cachedData) > 0 { logging.Debug("BLE fetch failed, serving partial data from cache", "VIN", vin, "CachedEndpoints", len(cachedData)) combinedResponse := make(map[string]json.RawMessage) for endpoint, data := range cachedData { combinedResponse[endpoint] = data - if err := json.Unmarshal(data, &cs); err != nil { - cs = models.ChargeState{} - } - if vehicleOutOfRange { - cs.ChargingState = "Disconnected" - } } - responseJson, err := json.Marshal(cs) + responseJson, err := json.Marshal(combinedResponse) if err != nil { response.Result = false response.Reason = apiResponse.Error @@ -298,13 +290,8 @@ func VehicleData(w http.ResponseWriter, r *http.Request) { response.Reason = "The request was partially processed from cache. Some data may be stale." response.Response = responseJson } else { - cs.ChargingState = "Disconnected" - responseJson, err := json.Marshal(cs) - if err != nil { - } response.Result = false response.Reason = apiResponse.Error - response.Response = responseJson } } }