Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fixes, missing commands and endpoints #95

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7fbcb32
Abort processing command if BLE scan does not find the vehicle
Lenart12 Jan 18, 2025
6ca3e0e
Add BLE connection status
Lenart12 Jan 18, 2025
94d1fd8
Refactor command handling and implement most missing commands
Lenart12 Jan 23, 2025
275f34b
Revert BLE connection timeout from 290 seconds (testing) to 29 seconds
Lenart12 Jan 23, 2025
82057ed
Add missing vehicle state conversion
Lenart12 Jan 27, 2025
87859d9
Update README to include all supported endpoints for data requests
Lenart12 Jan 27, 2025
5891119
Add small timeout to startInfotainmentSession
Lenart12 Jan 27, 2025
1133a03
Fix wrong context in startInfotainmentSession
Lenart12 Jan 27, 2025
a9df39b
Fix commands not retrying correctly if connection expires
Lenart12 Jan 27, 2025
e05c8ed
Update vehicle-command dependency to support BLE scanning
Lenart12 Jan 28, 2025
d669d15
Fix linter errors
Lenart12 Jan 28, 2025
0c365d9
Fix `session_info` and `add-key-request` not working
Lenart12 Jan 28, 2025
8f6fa1e
Change `wake_up` to be more like fleet command
Lenart12 Jan 29, 2025
9b7bdac
Add command line parsing
Lenart12 Jan 29, 2025
748647e
Allow keys directory to be specified
Lenart12 Jan 29, 2025
fe13af7
Update README and environment variables documentation with command li…
Lenart12 Jan 29, 2025
78d8d0d
Fix add-key-request failing to start because it was marked as infotai…
Lenart12 Jan 29, 2025
39f5307
Redirect root path to /dashboard
Lenart12 Jan 30, 2025
43c7863
Show route in 404
Lenart12 Jan 30, 2025
69d540a
Add ProxyBaseURL configuration and update routes to use it
Lenart12 Jan 30, 2025
c828462
Refactor configuration to separate Dashboard and API base URLs, updat…
Lenart12 Jan 30, 2025
fd5462d
Add support for `X-Ingress-Path` or `X-Forwarded-Prefix`
Lenart12 Jan 30, 2025
1bde67f
Update README to include Home Assistant addon installation instructions
Lenart12 Jan 30, 2025
10a5d24
Update README with new configuration options
Lenart12 Jan 30, 2025
dc233a6
Ignore json errors before validation
Lenart12 Jan 31, 2025
b971a05
Report null rssi instead of huge value if no connection
Lenart12 Feb 1, 2025
f7c26f0
README fixup
Lenart12 Feb 9, 2025
dc04bd0
Replace bluetooth backend
Lenart12 Feb 11, 2025
feadd14
Allow specifying different BT adapter
Lenart12 Feb 11, 2025
4449d12
Fix bug in `ble.ScanVehicleBeacon`
Lenart12 Feb 12, 2025
4c8ea6c
Update docker settings to use dbus not host network
Lenart12 Feb 12, 2025
64390c6
Prevent hanging on canceled requests
Lenart12 Feb 13, 2025
19f9878
Fix upstream deadlock
Lenart12 Feb 13, 2025
d2f011c
Fix method name from library API change
Lenart12 Feb 13, 2025
40cb54b
Ignore commands with canceled ctx inside operated connection
Lenart12 Feb 13, 2025
82c4113
Stop retry if context is canceled while retrying
Lenart12 Feb 19, 2025
c5ca3cb
Add retrying to connection status
Lenart12 Feb 19, 2025
aae890e
Fix scan getting stuck
Lenart12 Feb 19, 2025
9fa1db7
Add some logging
Lenart12 Feb 19, 2025
2137d79
Bump vehicle-command version
Lenart12 Feb 25, 2025
fdf944b
Show when connection status command finished
Lenart12 Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 88 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# TeslaBleHttpProxy

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).
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) or [TeslaBle2Mqtt](/~https://github.com/Lenart12/TeslaBle2Mqtt).

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)
- [Home assistant addon](#home-assistant-addon)
- [Docker compose](#docker-compose)
- [Build yourself](#build-yourself)
- [Generate key for vehicle](#generate-key-for-vehicle)
Expand All @@ -18,7 +19,14 @@ The program stores the received requests in a queue and processes them one by on

## 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))
You can either compile and use the Go program yourself or install it as a Home assistant addon or in a Docker container. ([detailed instruction](docs/installation.md))

### Home assistant addon

This proxy is availabile in the [TeslaBle2Mqtt-addon](/~https://github.com/Lenart12/TeslaBle2Mqtt-addon) repository, included as part of `TeslaBle2Mqtt` addon or as a standalone `TeslaBleHttpProxy` addon.

[![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=/~https://github.com/Lenart12/TeslaBle2Mqtt-addon)


### Docker compose

Expand All @@ -30,7 +38,7 @@ services:
image: wimaha/tesla-ble-http-proxy
container_name: tesla-ble-http-proxy
environment:
- cacheMaxAge=30 # Optional, but recommended to set this to anything more than 0 if you are using the vehicle data
- cacheMaxAge=5 # Optional, but recommended to set this to anything more than 0 if you are using the vehicle data
volumes:
- ~/TeslaBleHttpProxy/key:/key
- /var/run/dbus:/var/run/dbus
Expand All @@ -54,7 +62,29 @@ Download the code and save it in a folder named 'TeslaBleHttpProxy'. From there,

```
go build .
./TeslaBleHttpProxy
./TeslaBleHttpProxy -h
usage: TeslaBleHttpProxy [-h|--help] [-l|--logLevel "<value>"]
[-b|--httpListenAddress "<value>"] [-s|--scanTimeout
<integer>] [-c|--cacheMaxAge <integer>] [-k|--keys
"<value>"] [-d|--dashboardBaseUrl "<value>"]
[-a|--apiBaseUrl "<value>"]

Proxy for Tesla BLE commands over HTTP

Arguments:

-h --help Print help information
-l --logLevel Log level (DEBUG, INFO, WARN, ERROR, FATAL).
Default: INFO
-b --httpListenAddress HTTP bind address. Default: :8080
-s --scanTimeout Time in seconds to scan for BLE beacons during
device scan (0 = max). Default: 1
-c --cacheMaxAge Time in seconds for Cache-Control header (0 = no
cache). Default: 5
-k --keys Path to public and private keys. Default: key
-d --dashboardBaseUrl Base URL for dashboard (Useful if the proxy is
behind a reverse proxy). Default:
-a --apiBaseUrl Base URL for proxying API commands. Default:
```

Please remember to create an empty folder called `key` where the keys can be stored later.
Expand Down Expand Up @@ -120,24 +150,9 @@ If you want to use this proxy only for commands, and not for vehicle data, you c

### 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`.
The program uses the same interfaces as the Tesla [Fleet API](https://developer.tesla.com/docs/fleet-api#vehicle-commands). Currently, most commands are supported.

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` (`charge_start?wait=true`).

#### Example Request

Expand Down Expand Up @@ -177,20 +192,56 @@ If you want to receive specific data, you can add the endpoints to the request.

This is recommended if you want to receive data frequently, since it will reduce the time it takes to receive the data.

All of the supported endpoints are:
- charge_schedule_data
- charge_state
- climate_state
- closures_state
- drive_state
- location_data
- media_detail
- media
- parental_controls
- preconditioning_schedule_data
- software_update
- tire_pressure

### 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`
- `closure_statuses`
- `charge_port`
- `CLOSURESTATE_CLOSED`
- `CLOSURESTATE_OPEN`
- `CLOSURESTATE_AJAR`
- `CLOSURESTATE_UNKNOWN`
- `CLOSURESTATE_FAILED_UNLATCH`
- `CLOSURESTATE_OPENING`
- `CLOSURESTATE_CLOSING`
- `front_driver_door`
- ...
- `front_passenger_door`
- ...
- `front_trunk`
- ...
- `rear_driver_door`
- ...
- `rear_passenger_door`
- ...
- `rear_trunk`
- ...
- `tonneau`
- ...
- `vehicle_lock_state`
- `VEHICLELOCKSTATE_UNLOCKED`
- `VEHICLELOCKSTATE_LOCKED`
- `VEHICLELOCKSTATE_INTERNAL_LOCKED`
- `VEHICLELOCKSTATE_SELECTIVE_UNLOCKED`
- `vehicleSleepStatus`
- `vehicle_sleep_status`
- `VEHICLE_SLEEP_STATUS_UNKNOWN`
- `VEHICLE_SLEEP_STATUS_AWAKE`
- `VEHICLE_SLEEP_STATUS_ASLEEP`
- `userPresence`
- `user_presence`
- `VEHICLE_USER_PRESENCE_UNKNOWN`
- `VEHICLE_USER_PRESENCE_NOT_PRESENT`
- `VEHICLE_USER_PRESENCE_PRESENT`
Expand All @@ -200,4 +251,14 @@ The body controller state is fetched from the vehicle and returnes the state of
*(All requests with method GET.)*

Get body controller state:
`http://localhost:8080/api/1/vehicles/{VIN}/body_controller_state`
`http://localhost:8080/api/proxy/1/vehicles/{VIN}/body_controller_state`

### Connection status

Get BLE connection status of the vehicle
`GET http://localhost:8080/api/proxy/1/vehicles/LRWYGCFSXPC882647/connection_status`
- `address`
- `connectable`
- `local_name`
- `operated`
- `rssi`
85 changes: 60 additions & 25 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package config

import (
"fmt"
"net/url"
"os"
"strconv"

"github.com/akamensky/argparse"
"github.com/charmbracelet/log"
)

Expand All @@ -13,41 +15,74 @@ var PrivateKeyFile = "key/private.pem"
type Config struct {
LogLevel string
HttpListenAddress string
CacheMaxAge int // Seconds to cache BLE responses
ScanTimeout int // Seconds to scan for BLE beacons during device scan (0 = max)
CacheMaxAge int // Seconds to cache BLE responses
DashboardBaseURL string // Base URL for proxying dashboard (Useful if the proxy is behind a reverse proxy)
ApiBaseUrl string // Base URL for proxying BLE commands (Useful if the proxy is behind a reverse proxy)
}

var AppConfig *Config

func LoadConfig() *Config {
envLogLevel := os.Getenv("logLevel")
if envLogLevel == "debug" {
log.SetLevel(log.DebugLevel)
log.Debug("LogLevel set to debug")
parser := argparse.NewParser("TeslaBleHttpProxy", "Proxy for Tesla BLE commands over HTTP")
logLevel := parser.String("l", "logLevel", &argparse.Options{Help: "Log level (DEBUG, INFO, WARN, ERROR, FATAL)", Default: "INFO", Validate: func(args []string) error {
if _, err := log.ParseLevel(args[0]); err != nil {
return err
}
return nil
}})
httpListenAddress := parser.String("b", "httpListenAddress", &argparse.Options{Help: "HTTP bind address", Default: ":8080", Validate: func(args []string) error {
// Check if the proxy host is a valid URL
url, err := url.Parse(fmt.Sprintf("//%s", args[0]))
if err != nil {
return fmt.Errorf("invalid bind address (%s)", err)
}
if url.Path != "" {
return fmt.Errorf("bind address must not contain a path or scheme")
}
return nil
}})
scanTimeout := parser.Int("s", "scanTimeout", &argparse.Options{Help: "Time in seconds to scan for BLE beacons during device scan (0 = max)", Default: 1})
cacheMaxAge := parser.Int("c", "cacheMaxAge", &argparse.Options{Help: "Time in seconds for Cache-Control header (0 = no cache)", Default: 5})
keys := parser.String("k", "keys", &argparse.Options{Help: "Path to public and private keys", Default: "key", Validate: func(args []string) error {
f, err := os.Stat(args[0])
if err != nil {
return fmt.Errorf("failed to find keys directory (%s)", err)
}
if !f.IsDir() {
return fmt.Errorf("keys is not a directory")
}
return nil
}})
dashboardBaseUrl := parser.String("d", "dashboardBaseUrl", &argparse.Options{Help: "Base URL for dashboard (Useful if the proxy is behind a reverse proxy)", Default: ""})
apiBaseUrl := parser.String("a", "apiBaseUrl", &argparse.Options{Help: "Base URL for proxying API commands", Default: ""})
// Inject environment variables as command line arguments
args := os.Args
for _, arg := range parser.GetArgs() {
if arg.GetPositional() || arg.GetLname() == "help" {
continue
}
osArg := os.Getenv(arg.GetLname())
if osArg != "" {
args = append(args, fmt.Sprintf("--%s=%s", arg.GetLname(), osArg))
}
}
if envLogLevel == "" {
envLogLevel = "info"
}

addr := os.Getenv("httpListenAddress")
if addr == "" {
addr = ":8080"
}
log.Info("TeslaBleHttpProxy", "httpListenAddress", addr)

cacheMaxAge := os.Getenv("cacheMaxAge")
if cacheMaxAge == "" {
cacheMaxAge = "0" // default value
}
cacheMaxAgeInt, err := strconv.Atoi(cacheMaxAge)
err := parser.Parse(args)
if err != nil {
log.Error("Invalid cacheMaxAge value, using default (0)", "error", err)
cacheMaxAgeInt = 0
log.Fatal("Failed to parse arguments", "error", err)
}

PublicKeyFile = fmt.Sprintf("%s/public.pem", *keys)
PrivateKeyFile = fmt.Sprintf("%s/private.pem", *keys)

return &Config{
LogLevel: envLogLevel,
HttpListenAddress: addr,
CacheMaxAge: cacheMaxAgeInt,
LogLevel: *logLevel,
HttpListenAddress: *httpListenAddress,
ScanTimeout: *scanTimeout,
CacheMaxAge: *cacheMaxAge,
DashboardBaseURL: *dashboardBaseUrl,
ApiBaseUrl: *apiBaseUrl,
}
}

Expand Down
27 changes: 24 additions & 3 deletions docs/environment_variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@ You can optionally set environment variables to override the default behavior.

## logLevel

This is the log level. Options: debug (Default: info)
This is the log level. Options: debug (Default: INFO)

## cacheMaxAge

This is the number of seconds to cache the BLE responses for vehicle data and body controller state. If set to 0, the cache is disabled. (Default: 0)
This is the value that will be set in Cache-Control header for vehicle data and body controller state responses. If set to 0, the cache is disabled. (Default: 5)

## httpListenAddress

This is the address and port to listen for HTTP requests. (Default: :8080)

## keys

Path to public and private keys. (Default: key)

## dashboardBaseUrl

Base URL for dashboard (Useful if the proxy is behind a reverse proxy). (Default: empty)

## apiBaseUrl

Base URL for proxying API commands. (Default: empty)

> [!NOTE]
> It will adjust its base path depending on the `X-Ingress-Path` and `X-Forwarded-Prefix`
> headers, regardless of what either of the base url is set to.

# Example

## Docker compose
Expand All @@ -33,5 +49,10 @@ This will set the log level to debug, the cache max age to 30 seconds, and the H
You can also set the environment variables in the command line when starting the program. Example:

```
logLevel=debug cacheMaxAge=30 httpListenAddress=:5687 ./TeslaBleHttpProxy
./TeslaBleHttpProxy --logLevel=debug --cacheMaxAge=30 --httpListenAddress=:5687
```

## Caution

> [!WARNING]
> If you set both environment variables and command line options for the same setting, you will see the error `[command] can only be present once`
7 changes: 2 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ go 1.23.3
require (
github.com/charmbracelet/log v0.4.0
github.com/gorilla/mux v1.8.1
github.com/teslamotors/vehicle-command v0.2.1
github.com/teslamotors/vehicle-command v0.3.3-0.20250128004836-ebad42aaa852
)

require (
github.com/JuulLabs-OSS/cbgo v0.0.2 // indirect
github.com/akamensky/argparse v1.4.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.12.1 // indirect
github.com/charmbracelet/x/ansi v0.1.4 // indirect
Expand All @@ -33,7 +34,3 @@ require (
golang.org/x/sys v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

replace github.com/teslamotors/vehicle-command => github.com/wimaha/vehicle-command v0.0.4

replace github.com/go-ble/ble => github.com/wimaha/ble_BleConnectFix v0.0.0-20240822192426-3f74826c1268
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/JuulLabs-OSS/cbgo v0.0.1/go.mod h1:L4YtGP+gnyD84w7+jN66ncspFRfOYB5aj9QSXaFHmBA=
github.com/JuulLabs-OSS/cbgo v0.0.2 h1:gCDyT0+EPuI8GOFyvAksFcVD2vF4CXBAVwT6uVnD9oo=
github.com/JuulLabs-OSS/cbgo v0.0.2/go.mod h1:L4YtGP+gnyD84w7+jN66ncspFRfOYB5aj9QSXaFHmBA=
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
Expand All @@ -16,6 +18,8 @@ github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ble/ble v0.0.0-20240122180141-8c5522f54333 h1:bQK6D51cNzMSTyAf0HtM30V2IbljHTDam7jru9JNlJA=
github.com/go-ble/ble v0.0.0-20240122180141-8c5522f54333/go.mod h1:fFJl/jD/uyILGBeD5iQ8tYHrPlJafyqCJzAyTHNJ1Uk=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
Expand Down Expand Up @@ -67,6 +71,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/teslamotors/vehicle-command v0.3.3-0.20250128004836-ebad42aaa852 h1:TrYeM7qS2mB80Zl8T+xqlzxNG6dEmpRaK6oPrDGQBIo=
github.com/teslamotors/vehicle-command v0.3.3-0.20250128004836-ebad42aaa852/go.mod h1:ZVR0KE8v3IrQUJAuBrxKkRjPZOVI0oxEqBj8x1eFpDQ=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/wimaha/ble_BleConnectFix v0.0.0-20240822192426-3f74826c1268 h1:36sDJ2qts2oT/Fy/Wi6MR15C+LHl2+ZUzLp6UaqhS9c=
github.com/wimaha/ble_BleConnectFix v0.0.0-20240822192426-3f74826c1268/go.mod h1:fFJl/jD/uyILGBeD5iQ8tYHrPlJafyqCJzAyTHNJ1Uk=
Expand Down
Loading