├── tesla_http_proxy ├── rootfs │ ├── etc │ │ └── s6-overlay │ │ │ └── s6-rc.d │ │ │ ├── webui │ │ │ ├── type │ │ │ ├── dependencies.d │ │ │ │ └── base │ │ │ └── run │ │ │ └── user │ │ │ └── contents.d │ │ │ └── webui │ └── app │ │ ├── nginx_tesla.conf │ │ ├── const.py │ │ ├── templates │ │ ├── callback.html │ │ └── index.html │ │ ├── auth.py │ │ ├── webui.py │ │ └── run.sh ├── icon.png ├── logo.png ├── README.md ├── translations │ ├── en.yaml │ └── fr.yaml ├── Dockerfile ├── config.yaml ├── CHANGELOG.md └── DOCS.md ├── standalone ├── bashio │ └── addons.self.options.config.cache └── start_proxy.sh ├── scripts ├── build ├── setup └── clean_install ├── .gitignore ├── repository.yaml ├── .vscode ├── settings.json └── tasks.json ├── .github ├── dependabot.yaml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── workflows │ ├── lint.yaml │ └── builder.yaml ├── .devcontainer └── devcontainer.json ├── DEVELOPERS.md ├── README.md └── LICENSE /tesla_http_proxy/rootfs/etc/s6-overlay/s6-rc.d/webui/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /standalone/bashio/addons.self.options.config.cache: -------------------------------------------------------------------------------- 1 | {"regenerate_auth":true, "debug":true} -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | echo "Rebuilding addon..." 4 | ha addons rebuild local_tesla_http_proxy 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | standalone/nginx 3 | standalone/share 4 | standalone/ssl 5 | standalone/secrets.env 6 | -------------------------------------------------------------------------------- /tesla_http_proxy/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llamafilm/tesla-http-proxy-addon/HEAD/tesla_http_proxy/icon.png -------------------------------------------------------------------------------- /tesla_http_proxy/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llamafilm/tesla-http-proxy-addon/HEAD/tesla_http_proxy/logo.png -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | sudo apt install -y \ 6 | python3.11 \ 7 | python3-flask \ 8 | python3-requests 9 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/webui: -------------------------------------------------------------------------------- 1 | The existence of this file merely enables the s6-rc service by the same name 2 | -------------------------------------------------------------------------------- /repository.yaml: -------------------------------------------------------------------------------- 1 | name: Tesla HTTP Proxy 2 | url: 'https://github.com/llamafilm/tesla-http-proxy-addon' 3 | maintainer: Elliott Balsley 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "shellcheck.customArgs": [ 3 | "--shell=bash" 4 | ], 5 | "files.associations": { 6 | "*.yaml": "home-assistant" 7 | } 8 | } -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "06:00" 8 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/etc/s6-overlay/s6-rc.d/webui/dependencies.d/base: -------------------------------------------------------------------------------- 1 | The existence of this file tells s6-rc to only start webui when all the base services are ready: it prevents race conditions. 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Community Support 4 | url: https://github.com/llamafilm/tesla-http-proxy-addon/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/etc/s6-overlay/s6-rc.d/webui/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | 3 | # read options 4 | # you can pass in these variables if running without supervisor 5 | if [ -n "${HASSIO_TOKEN:-}" ]; then 6 | DOMAIN="$(bashio::config 'domain')"; export DOMAIN 7 | DEBUG="$(bashio::config 'debug')"; export DEBUG 8 | CLIENT_ID="$(bashio::config 'client_id')"; export CLIENT_ID 9 | CLIENT_SECRET="$(bashio::config 'client_secret')"; export CLIENT_SECRET 10 | REGION="$(bashio::config 'region')"; export REGION 11 | fi 12 | 13 | python3 /app/webui.py 14 | -------------------------------------------------------------------------------- /tesla_http_proxy/README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-on: Tesla HTTP Proxy 2 | 3 | This add-on runs the official [Tesla HTTP Proxy](https://github.com/teslamotors/vehicle-command) to allow Fleet API requests on modern vehicles. Please do not bother Tesla for support on this. 4 | 5 | ## About 6 | Runs through the Fleet API authorization procedure and then runs Tesla's HTTP Proxy code in Go. Also runs a simple web server that allows you to enroll your public key in your vehicle. 7 | 8 | Setting this up is fairly complex. Please read [DOCS.md](https://github.com/llamafilm/tesla-http-proxy-addon/blob/main/tesla_http_proxy/DOCS.md) for details. If you need help, you can try starting a discussion on GitHub. Please do not open an issue unless you've found a bug. 9 | -------------------------------------------------------------------------------- /scripts/clean_install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | echo "Uninstalling Nginx addon..." 4 | ha addons uninstall core_nginx_proxy 5 | sudo rm -rf /tmp/supervisor_data/share/nginx_proxy/* 6 | echo "Installing Nginx addon..." 7 | ha addons install core_nginx_proxy 8 | 9 | echo Uninstalling Tesla HTTP Proxy addon... 10 | ha addons uninstall local_tesla_http_proxy 11 | sudo rm -rf /tmp/supervisor_data/share/tesla/* 12 | echo Installing Tesla HTTP Proxy addon... 13 | ha addons install local_tesla_http_proxy 14 | 15 | echo "Installing Tesla Custom component..." 16 | sudo chmod 777 /tmp/supervisor_data/homeassistant 17 | git clone https://github.com/alandtse/tesla.git /tmp/supervisor_data/homeassistant/tesla 18 | ln -s tesla/custom_components /tmp/supervisor_data/homeassistant/custom_components 19 | ha core restart 20 | -------------------------------------------------------------------------------- /tesla_http_proxy/translations/en.yaml: -------------------------------------------------------------------------------- 1 | configuration: 2 | ssl: 3 | name: Enable SSL 4 | description: Enable usage of SSL on the webserver inside the add-on 5 | client_id: 6 | name: Client ID 7 | description: Client ID obtained by creating app at developer.tesla.com 8 | client_secret: 9 | name: Client Secret 10 | description: Client Secret obtained by creating app at developer.tesla.com 11 | debug: 12 | name: Debug Logging 13 | description: Print verbose messages to the log for debugging 14 | domain: 15 | name: Fully Qualified Domain Name 16 | description: Lowercase, without https, e.g. `tesla.example.com` 17 | regenerate_auth: 18 | name: Regenerate Tesla authentication 19 | description: This will be automatically disabled after first run 20 | region: 21 | name: Region 22 | network: 23 | 443/tcp: HTTPS Proxy for Fleet API 24 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/nginx_tesla.conf: -------------------------------------------------------------------------------- 1 | server { 2 | server_name __DOMAIN__; 3 | 4 | ssl_certificate /ssl/fullchain.pem; 5 | ssl_certificate_key /ssl/privkey.pem; 6 | 7 | # dhparams file 8 | ssl_dhparam /data/dhparams.pem; 9 | 10 | listen 443 ssl http2; 11 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 12 | 13 | location / { 14 | return 404; 15 | } 16 | 17 | # static public key for Tesla 18 | location /.well-known/appspecific/com.tesla.3p.public-key.pem { 19 | root /share/tesla; 20 | try_files /com.tesla.3p.public-key.pem =404; 21 | } 22 | 23 | location = /favicon.ico { 24 | log_not_found off; 25 | } 26 | 27 | location = /robots.txt { 28 | log_not_found off; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tesla_http_proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILD_FROM 2 | FROM $BUILD_FROM as builder 3 | 4 | RUN apk add --no-cache go git 5 | 6 | # install Tesla Go packages 7 | RUN git clone https://github.com/teslamotors/vehicle-command.git /vehicle-command 8 | WORKDIR /vehicle-command 9 | RUN git checkout cb07d7f792b14a079277d485cd0a5c46efc24ef0 10 | RUN go get ./... && \ 11 | go build ./... && \ 12 | go install ./... 13 | 14 | FROM $BUILD_FROM 15 | 16 | COPY --from=builder /root/go/bin/tesla-http-proxy /usr/bin/ 17 | COPY --from=builder /root/go/bin/tesla-keygen /usr/bin 18 | 19 | # install dependencies 20 | RUN apk add --no-cache \ 21 | python3 \ 22 | py3-flask \ 23 | py3-requests \ 24 | gpg-agent \ 25 | pass \ 26 | curl \ 27 | openssl 28 | 29 | # Python 3 HTTP Server serves the current working dir 30 | WORKDIR /data 31 | 32 | # Copy data for add-on 33 | COPY rootfs / 34 | RUN chmod a+x /app/run.sh 35 | 36 | CMD [ "/app/run.sh" ] 37 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/const.py: -------------------------------------------------------------------------------- 1 | """Constants""" 2 | 3 | SCOPES = 'openid offline_access vehicle_device_data vehicle_cmds vehicle_charging_cmds energy_device_data energy_cmds' 4 | AUDIENCES = { 5 | 'North America, Asia-Pacific': 'https://fleet-api.prd.na.vn.cloud.tesla.com', 6 | 'Europe, Middle East, Africa': 'https://fleet-api.prd.eu.vn.cloud.tesla.com', 7 | 'China' : 'https://fleet-api.prd.cn.vn.cloud.tesla.cn' 8 | } 9 | TESLA_AUTH_ENDPOINTS = { 10 | 'North America, Asia-Pacific': 'https://fleet-auth.prd.vn.cloud.tesla.com', 11 | 'Europe, Middle East, Africa': 'https://fleet-auth.prd.vn.cloud.tesla.com', 12 | 'China' : 'https://fleet-auth.prd.vn.cloud.tesla.com' 13 | } 14 | TESLA_AK_ENDPOINTS = { 15 | 'North America, Asia-Pacific': 'https://tesla.com', 16 | 'Europe, Middle East, Africa': 'https://tesla.com', 17 | 'China' : 'https://tesla.cn' 18 | } 19 | -------------------------------------------------------------------------------- /tesla_http_proxy/translations/fr.yaml: -------------------------------------------------------------------------------- 1 | configuration: 2 | ssl: 3 | name: Activer SSL 4 | description: Activer l'usage SSL pour le serveur-web du add-on 5 | client_id: 6 | name: Client ID 7 | description: Client ID obtenu lors de la creation de l'application sur developer.tesla.com 8 | client_secret: 9 | name: Client Secret 10 | description: Client Secret obtenu lors de la creation de l'application at developer.tesla.com 11 | debug: 12 | name: Journalisation du débogage 13 | description: Imprimer des messages détaillés dans le journal pour le débogage 14 | domain: 15 | name: Nom de domaine pleinement qualifié (FQDN) 16 | description: Lettre minuscule, sans https, i.e. `tesla.example.com` 17 | regenerate_auth: 18 | name: Regénérer la paire de clés et le certificat 19 | description: Cette option est automatiquement désactivée après la première exécution 20 | region: 21 | name: Région 22 | network: 23 | 443/tcp: HTTPS Proxy pour le Fleet API 24 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tesla HTTP Proxy add-on devcontainer", 3 | "image": "ghcr.io/home-assistant/devcontainer:addons", 4 | "appPort": ["7123:8123", "8099:8099"], 5 | "postCreateCommand": "scripts/setup", 6 | "postStartCommand": "bash devcontainer_bootstrap", 7 | "runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"], 8 | "containerEnv": { 9 | "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" 10 | }, 11 | "customizations": { 12 | "vscode": { 13 | "extensions": [ 14 | "ms-python.python", 15 | "ms-python.vscode-pylance", 16 | "timonwong.shellcheck", 17 | "esbenp.prettier-vscode" 18 | ], 19 | "settings": { 20 | "editor.formatOnPaste": false, 21 | "editor.formatOnSave": false, 22 | "editor.formatOnType": true, 23 | "files.trimTrailingWhitespace": true 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tesla_http_proxy/config.yaml: -------------------------------------------------------------------------------- 1 | name: "Tesla HTTP Proxy" 2 | version: "2.3.0" 3 | slug: "tesla_http_proxy" 4 | description: "Proxy requests for Tesla Fleet API" 5 | url: "https://github.com/llamafilm/tesla-http-proxy-addon/tree/main/tesla_http_proxy" 6 | arch: 7 | - armhf 8 | - armv7 9 | - aarch64 10 | - amd64 11 | - i386 12 | hassio_role: homeassistant 13 | init: false 14 | map: 15 | - share:rw 16 | startup: services 17 | options: 18 | client_id: "" 19 | client_secret: "" 20 | domain: "" 21 | debug: false 22 | regenerate_auth: true 23 | region: "North America, Asia-Pacific" 24 | schema: 25 | client_id: str? 26 | client_secret: password? 27 | domain: match(^[a-z0-9-.]{1,253}$) 28 | debug: bool 29 | regenerate_auth: bool 30 | region: list(North America, Asia-Pacific|Europe, Middle East, Africa|China) 31 | ingress: true 32 | panel_icon: mdi:forward 33 | backup_exclude: 34 | - /data/gnugpg 35 | - /data/password-store 36 | hassio_api: true 37 | stage: experimental 38 | ports: 39 | 443/tcp: 4430 40 | image: "ghcr.io/llamafilm/tesla_http_proxy_{arch}" 41 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Start Home Assistant", 6 | "type": "shell", 7 | "command": "supervisor_run", 8 | "group": { 9 | "kind": "test", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "new" 15 | }, 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "label": "Rebuild Addon", 20 | "type": "shell", 21 | "command": "scripts/build", 22 | "group": { 23 | "kind": "test", 24 | "isDefault": true 25 | }, 26 | "presentation": { 27 | "reveal": "always", 28 | "panel": "new" 29 | }, 30 | }, 31 | { 32 | "label": "Clean Install Addon", 33 | "type": "shell", 34 | "command": "scripts/clean_install", 35 | "group": { 36 | "kind": "test", 37 | "isDefault": true 38 | }, 39 | "presentation": { 40 | "reveal": "always", 41 | "panel": "new" 42 | }, 43 | } 44 | 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 0 * * *" 12 | 13 | jobs: 14 | find: 15 | name: Find add-ons 16 | runs-on: ubuntu-latest 17 | outputs: 18 | addons: ${{ steps.addons.outputs.addons_list }} 19 | steps: 20 | - name: ⤵️ Check out code from GitHub 21 | uses: actions/checkout@v4.1.6 22 | 23 | - name: 🔍 Find add-on directories 24 | id: addons 25 | uses: home-assistant/actions/helpers/find-addons@master 26 | 27 | lint: 28 | name: Lint add-on ${{ matrix.path }} 29 | runs-on: ubuntu-latest 30 | needs: find 31 | strategy: 32 | matrix: 33 | path: ${{ fromJson(needs.find.outputs.addons) }} 34 | steps: 35 | - name: ⤵️ Check out code from GitHub 36 | uses: actions/checkout@v4.1.6 37 | 38 | - name: 🚀 Run Home Assistant Add-on Lint 39 | uses: frenck/action-addon-linter@v2.15 40 | with: 41 | path: "./${{ matrix.path }}" 42 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/templates/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 31 | 32 | 33 |
Authorization complete. Refresh token is shown below and will be copied to clipboard when you click the button.
34 |
{{refresh_token}}
35 |
36 | 37 |
38 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DEVELOPERS.md: -------------------------------------------------------------------------------- 1 | ### Development 2 | 3 | Dev Container can be used for limited testing: 4 | 5 | 1. Open workspace in VS Code dev container 6 | 1. Cmd+Shift+P: Run Task: Start Home Assistant 7 | 1. Open web browser on port 7123 8 | 1. Run through Home Assitant initialization wizard 9 | 1. Enable advanced mode in user settings 10 | 1. Cmd+Shift+P: Run Taks: Rebuild Addon 11 | 12 | ### Testing 13 | 14 | To test the proxy, you can make requests from inside the Home Assistant container. First get the access token, which will expire in a few hours: 15 | 16 | ``` 17 | TESLA_AUTH_TOKEN=$(docker exec -ti addon_c03d64a7_tesla_http_proxy cat access_token) 18 | curl --cacert /share/tesla/selfsigned.pem \ 19 | --header "Authorization: Bearer $TESLA_AUTH_TOKEN" \ 20 | "https://c03d64a7-tesla-http-proxy/api/1/vehicles" 21 | ``` 22 | 23 | ### Standalone Usage 24 | 25 | While this addon is meant to run in HAOS, it may be useful to run as a standalone Docker container, for CI pipelines or debugging the Tesla integration in a dev container. This is a work-in-progress script to do that. 26 | 27 | Th `start_proxy.sh` script will start 2 Docker containers, one for Nginx and one for the proxy. It mimics some HAOS concepts including folder structure and bashio so you can use the same Docker image as the addon. You may need to modify according to your environemnt. 28 | 29 | - Forward https://DOMAIN:443 to localhost:4430 30 | - Start Docker 31 | - Clone this repo 32 | - Navigate to the `standalone` folder 33 | - Copy TLS cert and key to `ssl/fullchain.pem` and `ssl/privkey.pem` 34 | - Set environment variables in `secrets.env` 35 | - Run `start_proxy.sh` 36 | - Start OAuth at http://localhost:8099 and follow instructions in [DOCS.md](tesla_http_proxy/DOCS.md). 37 | - After getting the token, edit `addons.self.options.config.cache` to set `regenerate_auth` to false and then restart 38 | -------------------------------------------------------------------------------- /standalone/start_proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # see DEVELOPERS.md for instructions 4 | 5 | ######################################################### 6 | # Define these variables in a new file called secrets.env 7 | ARCH=aarch64 # options: aarch64, amd64, armhf, armv7, i386 8 | DEBUG=true # must be lowercase 9 | REGION="North America, Asia-Pacific" # options defined in const.py 10 | DOMAIN= 11 | CLIENT_ID= 12 | CLIENT_SECRET= 13 | ######################################################### 14 | 15 | set -e 16 | cd "$(dirname "$0")" 17 | source secrets.env 18 | mkdir -p nginx 19 | # start nginx container 20 | openssl dhparam -dsaparam -out nginx/dhparams.pem 2048 21 | sed "s/__DOMAIN__/${DOMAIN}/g; s/__PROXYHOST__/tesla_http_proxy/g" ../tesla_http_proxy/rootfs/app/nginx_tesla.conf > nginx/nginx_tesla.conf 22 | 23 | echo "Making sure we have a clean start if needed ..." 24 | docker rm -f nginx 25 | docker rm -f tesla_http_proxy 26 | docker network rm tesla 27 | 28 | 29 | echo "Create docker network Tesla...." 30 | docker network create tesla 31 | echo "Starting nginx container..." 32 | 33 | docker run --rm --name nginx -d -p 4430:443 -e DOMAIN="$DOMAIN" --network tesla \ 34 | -v ./ssl:/ssl:ro \ 35 | -v ./share:/share:ro \ 36 | -v ./nginx/nginx_tesla.conf:/etc/nginx/conf.d/nginx_tesla.conf:ro \ 37 | -v ./nginx/dhparams.pem:/data/dhparams.pem:ro \ 38 | nginx 39 | 40 | ## build from source while developing proxy 41 | # git clone https://github.com/llamafilm/tesla-http-proxy-addon.git 42 | # docker build --build-arg BUILD_FROM=ghcr.io/home-assistant/${ARCH}-homeassistant-base tesla-http-proxy-addon/tesla_http_proxy 43 | 44 | # start proxy container 45 | # fake token used for Tesla integration CI tests 46 | docker run --rm --name tesla_http_proxy -p 8099:8099 -p 443:443 --network tesla \ 47 | -v ./share:/share \ 48 | -v ./bashio:/tmp/.bashio:ro \ 49 | -e DOMAIN="${DOMAIN}" \ 50 | -e CLIENT_ID="${CLIENT_ID}" \ 51 | -e CLIENT_SECRET="${CLIENT_SECRET}" \ 52 | -e REGION="${REGION}" \ 53 | -e SUPERVISOR_TOKEN="fake-token" \ 54 | -e DEBUG="${DEBUG}" \ 55 | ghcr.io/llamafilm/tesla_http_proxy_"${ARCH}":2.2.7 56 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 45 | 46 | 47 |

Tesla HTTP Proxy add-on

48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 |
62 | 65 | 68 |
69 | 70 | 76 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/auth.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import requests 4 | 5 | from const import ( 6 | SCOPES, 7 | AUDIENCES, 8 | TESLA_AUTH_ENDPOINTS, 9 | ) 10 | 11 | SUPERVISOR_TOKEN = os.environ['SUPERVISOR_TOKEN'] 12 | CLIENT_ID = os.environ['CLIENT_ID'] 13 | CLIENT_SECRET = os.environ['CLIENT_SECRET'] 14 | DOMAIN = os.environ['DOMAIN'] 15 | REGION = os.environ['REGION'] 16 | DEBUG = os.environ['DEBUG'] 17 | AUDIENCE = AUDIENCES[REGION] 18 | TESLA_AUTH_ENDPOINT = TESLA_AUTH_ENDPOINTS[REGION] 19 | 20 | 21 | if DEBUG == 'true': 22 | log_level = logging.DEBUG 23 | else: 24 | log_level = logging.INFO 25 | logging.basicConfig(format='[%(asctime)s] %(name)s:%(levelname)s: %(message)s', 26 | level=log_level, datefmt='%H:%M:%S') 27 | logger = logging.getLogger('auth') 28 | 29 | # generate partner authentication token 30 | logger.info('Generating Partner Authentication Token') 31 | 32 | req = requests.post(f"{TESLA_AUTH_ENDPOINT}/oauth2/v3/token", 33 | headers={ 34 | 'Content-Type': 'application/x-www-form-urlencoded'}, 35 | data={ 36 | 'grant_type': 'client_credentials', 37 | 'client_id': CLIENT_ID, 38 | 'client_secret': CLIENT_SECRET, 39 | 'scope': SCOPES, 40 | 'audience': AUDIENCE 41 | } 42 | ) 43 | if req.status_code >= 400: 44 | logger.error("HTTP %s: %s", req.status_code, req.reason) 45 | logger.debug(req.text) 46 | try: 47 | tesla_api_token = req.json()['access_token'] 48 | except KeyError: 49 | logger.error("Response did not include access token: %s", req.text) 50 | raise SystemExit(1) 51 | 52 | # register Tesla account to enable API access 53 | logger.info('Registering Tesla account...') 54 | req = requests.post(f'{AUDIENCE}/api/1/partner_accounts', 55 | headers={ 56 | 'Authorization': 'Bearer ' + tesla_api_token, 57 | 'Content-Type': 'application/json' 58 | }, 59 | data='{"domain": "%s"}' % DOMAIN 60 | ) 61 | if req.status_code >= 400: 62 | logger.error("Error %s: %s", req.status_code, req.text) 63 | raise SystemExit(1) 64 | logger.debug(req.text) 65 | 66 | if os.environ.get('HASSIO_TOKEN'): 67 | # disable regenerate_auth to skip Python code on next launch 68 | req = requests.get('http://supervisor/addons/self/options/config', 69 | headers={ 70 | 'Authorization': f'Bearer {SUPERVISOR_TOKEN}' 71 | } 72 | ) 73 | options = req.json()['data'] 74 | options['regenerate_auth'] = False 75 | 76 | req = requests.post('http://supervisor/addons/self/options', 77 | headers={ 78 | 'Authorization': f'Bearer {SUPERVISOR_TOKEN}' 79 | }, 80 | json={ 81 | 'options': options 82 | } 83 | ) 84 | -------------------------------------------------------------------------------- /tesla_http_proxy/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 2.3.0 4 | 5 | ### Changed 6 | 7 | - Change to new API auth endpoint 8 | 9 | ## 2.2.8 10 | 11 | ### Changed 12 | 13 | - Bump [vehicle-command version](https://github.com/teslamotors/vehicle-command/pull/259) to allow exporting private key 14 | - Standalone usage without supervisor 15 | 16 | ## 2.2.6 17 | 18 | ### Changed 19 | 20 | - Retry public key check for 1 hour if HTTP request fails 21 | 22 | ## 2.2.3 23 | 24 | ### Changed 25 | 26 | - Use `.cn` endpoints in China 27 | - Nginx returns 404 on `/` location 28 | 29 | ## 2.2.0 30 | 31 | ### Added 32 | 33 | - Add webpage to generate refresh tokens 34 | 35 | ### Changed 36 | 37 | - Move troubleshooting steps to wiki page 38 | 39 | ## 2.1.1 40 | 41 | ### Added 42 | 43 | - Add debug config option to control log level 44 | 45 | ## 2.0.0 46 | 47 | ### Changed 48 | 49 | - Run Web UI as a separate s6-rc service so it's always available 50 | - Simplify nginx config so it no longer needs to access port 8099 51 | 52 | ### Removed 53 | 54 | - Removed OAuth flow because it was too complicated for most users to setup. Use a separate app to obtain refresh token. 55 | 56 | ## 1.3.7 57 | 58 | ### Changed 59 | 60 | - Log better error message when "registering Tesla account" 61 | 62 | ## 1.3.6 63 | 64 | ### Changed 65 | 66 | - Nginx: ignore bad requests without logging 67 | - Remove supervisor dependency, to allow running as standalone Docker container 68 | 69 | ## 1.3.4 70 | 71 | ### Changed 72 | 73 | - Fail early if public key doesn't work 74 | - Add scopes for energy sites (untested) 75 | - Copy refresh token to clipboard to simplify auth flow 76 | 77 | ## 1.3.3 78 | 79 | ### Added 80 | 81 | - Support `window_control` with vehicle-command 77d5cf3 82 | 83 | ### Changed 84 | 85 | - Reduce image size by using separate build stage 86 | 87 | ## 1.3.1 88 | 89 | ### Changed 90 | 91 | - Simplify config flow to avoid 502 error 92 | - Make credentials optional on first launch because Tesla requires public key before approving app request 93 | - Clarify instructions 94 | 95 | ## 1.2.2 96 | 97 | ### Changed 98 | 99 | - Support all 3 Fleet API regions 100 | - Colored output to help configuration of `tesla_custom` integration 101 | 102 | ## 1.2.0 103 | 104 | ### Changed 105 | 106 | - Remove unnecessary VIN from config 107 | - Add `regenerate_auth` config option to help with OAuth testing 108 | - Expose ports 443 and 8099 to support external reverse proxies 109 | - Improved error handling when add-on is restarted 110 | 111 | ## 1.1.0 112 | 113 | ### Changed 114 | 115 | - Skip auth flow if already completed 116 | - Print `refresh_token` to the log 117 | - Correct CN for SSL cert 118 | 119 | ## 1.0.0 120 | 121 | - Initial release 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tesla HTTP Proxy add-on 2 | 3 | Update January 2025: This addon is no longer maintained, as I sold my Tesla. A new Tesla Fleet integration has been added to core, which may meet your needs: https://www.home-assistant.io/integrations/tesla_fleet/ 4 | 5 | [![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%3A%2F%2Fgithub.com%2Fllamafilm%2Ftesla-http-proxy-addon) 6 | 7 | ## Add-ons 8 | 9 | This repository contains the following add-ons 10 | 11 | ### [Tesla HTTP Proxy](./tesla_http_proxy) 12 | 13 | ![Supports aarch64 Architecture][aarch64-shield] 14 | ![Supports amd64 Architecture][amd64-shield] 15 | ![Supports armhf Architecture][armhf-shield] 16 | ![Supports armv7 Architecture][armv7-shield] 17 | ![Supports i386 Architecture][i386-shield] 18 | 19 | ![Reported Installations][installations-shield-stable] 20 | 21 | 22 | 39 | 40 | [aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg 41 | [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg 42 | [armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg 43 | [armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg 44 | [i386-shield]: https://img.shields.io/badge/i386-yes-green.svg 45 | 46 | [installations-shield-stable]: https://img.shields.io/badge/dynamic/json?url=https://analytics.home-assistant.io/addons.json&query=$["c03d64a7_tesla_http_proxy"].total&label=Reported%20Installations&link=https://analytics.home-assistant.io/add-ons 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report an issue with this addon 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | This issue form is for reporting bugs only! Please use Discussions for support. 8 | - type: textarea 9 | validations: 10 | required: true 11 | attributes: 12 | label: The problem 13 | description: >- 14 | Provide a clear and concise description of what you did and what happened. 15 | - type: markdown 16 | attributes: 17 | value: | 18 | ## Environment 19 | - type: input 20 | id: addon_version 21 | validations: 22 | required: true 23 | attributes: 24 | label: What version of the addon are you using? 25 | description: > 26 | Can be found in: Settings ⇒ Addons ⇒ Tesla HTTP Proxy. 27 | - type: input 28 | validations: 29 | required: true 30 | attributes: 31 | label: What version of Home Assistant Core are you using? 32 | placeholder: core- 33 | description: > 34 | Can be found in: Settings ⇒ System ⇒ Repairs ⇒ Three Dots in Upper Right ⇒ System information. 35 | - type: dropdown 36 | validations: 37 | required: true 38 | attributes: 39 | label: What type of installation are you running? 40 | description: > 41 | Can be found in: Settings ⇒ System ⇒ Repairs ⇒ Three Dots in Upper Right ⇒ System information. 42 | options: 43 | - Home Assistant OS 44 | - Home Assistant Container 45 | - Home Assistant Supervised 46 | - Home Assistant Core 47 | - type: input 48 | attributes: 49 | label: What version of Tesla integration are you using? 50 | description: > 51 | Can be found in: HACS ⇒ Integrations ⇒ Tesla. (if applicabale) 52 | - type: dropdown 53 | validations: 54 | required: true 55 | attributes: 56 | label: What region of Tesla API are you using? 57 | options: 58 | - North America, Asia-Pacific 59 | - Europe, Middle East 60 | - China 61 | - type: input 62 | validations: 63 | required: true 64 | attributes: 65 | label: What model and year is your vehicle? 66 | - type: input 67 | validations: 68 | required: true 69 | attributes: 70 | label: What is your domain name (FQDN)? 71 | description: > 72 | This field is required for help related to DNS or TLS. Your 73 | public IP is likely being scanned by Chinese bots already, so there is 74 | no reason to hide it. 75 | - type: markdown 76 | attributes: 77 | value: | 78 | ## Details 79 | - type: textarea 80 | attributes: 81 | label: Paste the log output from the addon here 82 | description: Please redact tokens, but nothing else 83 | render: text 84 | - type: textarea 85 | attributes: 86 | label: Any additional context or screenshots that help explain the issue 87 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/webui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import random 4 | import string 5 | from urllib.parse import urlparse, parse_qs 6 | from flask import cli, Flask, render_template, request 7 | from werkzeug.exceptions import HTTPException 8 | import requests 9 | 10 | from const import ( 11 | SCOPES, 12 | AUDIENCES, 13 | TESLA_AUTH_ENDPOINTS, 14 | TESLA_AK_ENDPOINTS, 15 | ) 16 | 17 | app = Flask(__name__) 18 | 19 | DOMAIN = os.environ['DOMAIN'] 20 | DEBUG = os.environ['DEBUG'] 21 | CLIENT_ID = os.environ['CLIENT_ID'] 22 | CLIENT_SECRET = os.environ['CLIENT_SECRET'] 23 | REGION = os.environ['REGION'] 24 | AUDIENCE = AUDIENCES[REGION] 25 | TESLA_AUTH_ENDPOINT = TESLA_AUTH_ENDPOINTS[REGION] 26 | TESLA_AK_ENDPOINT = TESLA_AK_ENDPOINTS[REGION] 27 | 28 | BLUE = "\u001b[34m" 29 | RESET = "\x1b[0m" 30 | 31 | if DEBUG == 'true': 32 | log_level = logging.DEBUG 33 | else: 34 | log_level = logging.INFO 35 | logging.basicConfig(format='[%(asctime)s] %(name)s:%(levelname)s: %(message)s', 36 | level=log_level, datefmt='%H:%M:%S') 37 | logger = logging.getLogger('webui') 38 | 39 | @app.errorhandler(Exception) 40 | def handle_exception(e): 41 | """Exception handler for HTTP requests""" 42 | 43 | app.logger.error(e) 44 | # pass through HTTP errors 45 | if isinstance(e, HTTPException): 46 | return e 47 | 48 | # now you're handling non-HTTP exceptions only 49 | return 'Unknown Error', 500 50 | 51 | 52 | @app.route('/') 53 | def index(): 54 | """Web UI for add-on inside Home Assistant""" 55 | 56 | slug = os.uname().nodename.replace('-', '_') 57 | randomstate = ''.join(random.choices(string.hexdigits.lower(), k=10)) 58 | randomnonce = ''.join(random.choices(string.hexdigits.lower(), k=10)) 59 | 60 | return render_template('index.html', slug=slug, domain=DOMAIN, client_id=CLIENT_ID, 61 | scopes=SCOPES, randomstate=randomstate, randomnonce=randomnonce, 62 | auth_endpoint=TESLA_AUTH_ENDPOINT, ak_endpoint=TESLA_AK_ENDPOINT) 63 | 64 | 65 | @app.route('/callback') 66 | def callback(): 67 | """Handle callback from Tesla server to complete OAuth""" 68 | 69 | url = request.args.get('callback_url') 70 | app.logger.debug('Callback URL: %s', url) 71 | # sometimes I don't get a valid code, not sure why 72 | try: 73 | parsed_url = urlparse(url) 74 | query_params = parse_qs(parsed_url.query) 75 | code = query_params['code'][0] 76 | app.logger.debug('code: %s', code) 77 | except KeyError: 78 | return 'Invalid code!', 400 79 | 80 | # Exchange code for refresh_token 81 | req = requests.post(f"{TESLA_AUTH_ENDPOINT}/oauth2/v3/token", 82 | headers={ 83 | 'Content-Type': 'application/x-www-form-urlencoded'}, 84 | data={ 85 | 'grant_type': 'authorization_code', 86 | 'client_id': CLIENT_ID, 87 | 'client_secret': CLIENT_SECRET, 88 | 'code': code, 89 | 'audience': AUDIENCE, 90 | 'redirect_uri': f"https://{DOMAIN}/callback" 91 | } 92 | ) 93 | if req.status_code >= 400: 94 | logger.error("HTTP %s: %s", req.status_code, req.reason) 95 | response = req.json() 96 | refresh_token = response['refresh_token'] 97 | app.logger.warning("Obtained refresh token: %s", refresh_token) 98 | 99 | with open('/data/refresh_token', 'w') as f: 100 | f.write(response['refresh_token']) 101 | with open('/data/access_token', 'w') as f: 102 | f.write(response['access_token']) 103 | 104 | return render_template('callback.html', refresh_token=refresh_token) 105 | 106 | 107 | if __name__ == '__main__': 108 | logger.info('Starting Flask server for Web UI...') 109 | cli.show_server_banner = lambda *_: None 110 | app.run(port=8099, debug=False, host='0.0.0.0') 111 | -------------------------------------------------------------------------------- /tesla_http_proxy/rootfs/app/run.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | set -e 3 | 4 | # wait for webui.py to avoid interleaved log output 5 | sleep 2 6 | 7 | # read options 8 | # you can pass in these variables if running without supervisor 9 | if [ -n "${HASSIO_TOKEN:-}" ]; then 10 | CLIENT_ID="$(bashio::config 'client_id')"; export CLIENT_ID 11 | CLIENT_SECRET="$(bashio::config 'client_secret')"; export CLIENT_SECRET 12 | DOMAIN="$(bashio::config 'domain')"; export DOMAIN 13 | REGION="$(bashio::config 'region')"; export REGION 14 | DEBUG="$(bashio::config 'debug')"; export DEBUG 15 | fi 16 | 17 | export GNUPGHOME=/data/gnugpg 18 | export PASSWORD_STORE_DIR=/data/password-store 19 | 20 | generate_keypair() { 21 | # add custom config to Nginx if it's installed 22 | if ping -c 1 core-nginx-proxy >/dev/null 2>&1; then 23 | bashio::log.info "Current Nginx configuration" 24 | bashio::addon.options core_nginx_proxy 25 | echo 26 | 27 | bashio::log.info "Adding custom config to /share/nginx_proxy/nginx_tesla.conf" 28 | mkdir -p /share/nginx_proxy 29 | sed "s/__DOMAIN__/${DOMAIN}/g; s/__PROXYHOST__/${HOSTNAME}/g" /app/nginx_tesla.conf > /share/nginx_proxy/nginx_tesla.conf 30 | cat /share/nginx_proxy/nginx_tesla.conf 31 | else 32 | bashio::log.warning "Nginx is not running" 33 | fi 34 | 35 | # generate self signed SSL certificate 36 | bashio::log.info "Generating self-signed SSL certificate" 37 | openssl req -x509 -nodes -newkey ec \ 38 | -pkeyopt ec_paramgen_curve:secp521r1 \ 39 | -pkeyopt ec_param_enc:named_curve \ 40 | -subj "/CN=${HOSTNAME}" \ 41 | -keyout /data/key.pem -out /data/cert.pem -sha256 -days 3650 \ 42 | -addext "extendedKeyUsage = serverAuth" \ 43 | -addext "keyUsage = digitalSignature, keyCertSign, keyAgreement" 44 | mkdir -p /share/tesla 45 | cp /data/cert.pem /share/tesla/selfsigned.pem 46 | 47 | # Generate keypair 48 | bashio::log.info "Generating keypair" 49 | /usr/bin/tesla-keygen -f -keyring-type pass -key-name myself create > /share/tesla/com.tesla.3p.public-key.pem 50 | cat /share/tesla/com.tesla.3p.public-key.pem 51 | } 52 | 53 | # run on first launch only 54 | if ! pass > /dev/null 2>&1; then 55 | bashio::log.info "Setting up GnuPG and password-store" 56 | # shellcheck disable=SC2174 57 | mkdir -m 700 -p /data/gnugpg 58 | gpg --batch --passphrase '' --quick-gen-key myself default default 59 | gpg --list-keys 60 | pass init myself 61 | generate_keypair 62 | 63 | # verify certificate is not from previous install 64 | elif [ -f /share/tesla/com.tesla.3p.public-key.pem ] && [ -f /share/tesla/selfsigned.pem ]; then 65 | certPubKey="$(openssl x509 -noout -pubkey -in /share/tesla/selfsigned.pem)" 66 | keyPubKey="$(openssl pkey -pubout -in /data/key.pem)" 67 | if [ "${certPubKey}" == "${keyPubKey}" ]; then 68 | bashio::log.info "Found existing keypair" 69 | else 70 | bashio::log.warning "Existing certificate is invalid" 71 | generate_keypair 72 | fi 73 | else 74 | generate_keypair 75 | fi 76 | 77 | # verify public key is accessible with valid TLS cert 78 | bashio::log.info "Testing public key..." 79 | if ! curl -fD - --no-progress-meter --max-time 5 --connect-timeout 2 --retry 14 --retry-all-errors "https://$DOMAIN/.well-known/appspecific/com.tesla.3p.public-key.pem"; then 80 | bashio::log.fatal "Fix public key before proceeding." 81 | exit 1 82 | fi 83 | 84 | if [ -z "$CLIENT_ID" ]; then 85 | bashio::log.notice "Request application access with Tesla, then fill in credentials and restart addon." 86 | else 87 | if bashio::config.true regenerate_auth; then 88 | bashio::log.info "Running auth.py" 89 | python3 /app/auth.py 90 | fi 91 | 92 | bashio::log.info "Starting Tesla HTTP Proxy" 93 | if bashio::config.true debug; then 94 | /usr/bin/tesla-http-proxy -keyring-debug -keyring-type pass -key-name myself -cert /data/cert.pem -tls-key /data/key.pem -port 443 -host 0.0.0.0 -verbose 95 | else 96 | /usr/bin/tesla-http-proxy -keyring-debug -keyring-type pass -key-name myself -cert /data/cert.pem -tls-key /data/key.pem -port 443 -host 0.0.0.0 97 | fi 98 | fi 99 | -------------------------------------------------------------------------------- /.github/workflows/builder.yaml: -------------------------------------------------------------------------------- 1 | name: Builder 2 | 3 | env: 4 | BUILD_ARGS: "--test" 5 | MONITORED_FILES: "build.yaml config.yaml Dockerfile rootfs" 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | init: 17 | runs-on: ubuntu-latest 18 | name: Initialize builds 19 | outputs: 20 | changed_addons: ${{ steps.changed_addons.outputs.addons }} 21 | changed: ${{ steps.changed_addons.outputs.changed }} 22 | steps: 23 | - name: Check out the repository 24 | uses: actions/checkout@v4.1.6 25 | 26 | - name: Get changed files 27 | id: changed_files 28 | uses: jitterbit/get-changed-files@v1 29 | 30 | - name: Find add-on directories 31 | id: addons 32 | uses: home-assistant/actions/helpers/find-addons@master 33 | 34 | - name: Get changed add-ons 35 | id: changed_addons 36 | run: | 37 | declare -a changed_addons 38 | for addon in ${{ steps.addons.outputs.addons }}; do 39 | if [[ "${{ steps.changed_files.outputs.all }}" =~ $addon ]]; then 40 | for file in ${{ env.MONITORED_FILES }}; do 41 | if [[ "${{ steps.changed_files.outputs.all }}" =~ $addon/$file ]]; then 42 | if [[ ! "${changed_addons[@]}" =~ $addon ]]; then 43 | changed_addons+=("\"${addon}\","); 44 | fi 45 | fi 46 | done 47 | fi 48 | done 49 | 50 | changed=$(echo ${changed_addons[@]} | rev | cut -c 2- | rev) 51 | 52 | if [[ -n ${changed} ]]; then 53 | echo "Changed add-ons: $changed"; 54 | echo "changed=true" >> $GITHUB_OUTPUT; 55 | echo "addons=[$changed]" >> $GITHUB_OUTPUT; 56 | else 57 | echo "No add-on had any monitored files changed (${{ env.MONITORED_FILES }})"; 58 | fi 59 | build: 60 | needs: init 61 | runs-on: ubuntu-latest 62 | if: needs.init.outputs.changed == 'true' 63 | name: Build ${{ matrix.arch }} ${{ matrix.addon }} add-on 64 | strategy: 65 | matrix: 66 | addon: ${{ fromJson(needs.init.outputs.changed_addons) }} 67 | arch: ["aarch64", "amd64", "armhf", "armv7", "i386"] 68 | 69 | steps: 70 | - name: Check out repository 71 | uses: actions/checkout@v4.1.6 72 | 73 | - name: Get information 74 | id: info 75 | uses: home-assistant/actions/helpers/info@master 76 | with: 77 | path: "./${{ matrix.addon }}" 78 | 79 | - name: Check if add-on should be built 80 | id: check 81 | run: | 82 | if [[ "${{ steps.info.outputs.architectures }}" =~ ${{ matrix.arch }} ]]; then 83 | echo "build_arch=true" >> $GITHUB_OUTPUT; 84 | echo "image=$(echo ${{ steps.info.outputs.image }} | cut -d'/' -f3)" >> $GITHUB_OUTPUT; 85 | if [[ -z "${{ github.head_ref }}" ]] && [[ "${{ github.event_name }}" == "push" ]]; then 86 | echo "BUILD_ARGS=" >> $GITHUB_ENV; 87 | fi 88 | else 89 | echo "${{ matrix.arch }} is not a valid arch for ${{ matrix.addon }}, skipping build"; 90 | echo "build_arch=false" >> $GITHUB_OUTPUT; 91 | fi 92 | 93 | - name: Login to GitHub Container Registry 94 | if: env.BUILD_ARGS != '--test' 95 | uses: docker/login-action@v3.1.0 96 | with: 97 | registry: ghcr.io 98 | username: ${{ github.repository_owner }} 99 | password: ${{ secrets.GITHUB_TOKEN }} 100 | 101 | - name: Build ${{ matrix.addon }} add-on 102 | if: steps.check.outputs.build_arch == 'true' 103 | uses: home-assistant/builder@2024.03.5 104 | with: 105 | args: | 106 | ${{ env.BUILD_ARGS }} \ 107 | --${{ matrix.arch }} \ 108 | --target /data/${{ matrix.addon }} \ 109 | --image "${{ steps.check.outputs.image }}" \ 110 | --docker-hub "ghcr.io/${{ github.repository_owner }}" \ 111 | --addon 112 | -------------------------------------------------------------------------------- /tesla_http_proxy/DOCS.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-on: Tesla HTTP Proxy 2 | 3 | ## Prerequisites 4 | 5 | You must have a domain name (FQDN) with a valid SSL certificate to host your public key on standard port 443. The service running on this port (Nginx) must be accessible 7/24 for the vehicle to download that key *every time* a command is sent. 6 | For advanced users, there are many ways to set this up, but this guide assumes Home Assistant is accessible at `https://home.example.com` using [Nginx SSL proxy](https://github.com/home-assistant/addons/blob/master/nginx_proxy/DOCS.md). Add an additional `A` or `CNAME` record for `tesla.example.com` pointing to the same IP address, and an SSL certificate. You can use [Lets Encrypt](https://github.com/home-assistant/addons/blob/master/letsencrypt/DOCS.md) to make a wildcard certificate that covers both. 7 | 8 | Configure Nginx like this: 9 | ```yml 10 | domain: home.example.com 11 | hsts: max-age=31536000; includeSubDomains 12 | certfile: fullchain.pem 13 | keyfile: privkey.pem 14 | cloudflare: false 15 | customize: 16 | active: true 17 | default: nginx_proxy_default*.conf 18 | servers: nginx_proxy/*.conf 19 | ``` 20 | 21 | The `home.example.com` domain is not used by this addon so it doesn't matter what you enter there; The next steps will add an additional config file to host the public key at `tesla.example.com`. 22 | 23 | 24 | ## How to use 25 | 26 | Configure this addon with your domain name, then hit Start. It will initialize and then stop itself after a few seconds. Refresh the page to verify it stopped, then restart the Nginx addon so it loads the new config. Ignore the error: _Failed to restart add-on_. 27 | 28 | At this point your public key should be visible at `https://tesla.example.com/.well-known/appspecific/com.tesla.3p.public-key.pem`, which is required for Tesla's verification process. If this doesn't work, or if it shows any TLS certificate errors, you must fix that before proceeding further. 29 | 30 | Request application access at [developer.tesla.com](https://developer.tesla.com). My request was approved immediately but YMMV. This is currently free but it's possible they will monetize it in the future. You will need to provide the following information: 31 | 32 | - Name of your legal entity (first and last name is fine) 33 | - App Name, Description, Purpose (can be anything) 34 | - Allow all scopes 35 | - **OAuth Grant Type**: Authorization code and machine-to-machine 36 | - **Allowed Origin**: The FQDN where you are hosting the public key. Must be lowercase, e.g. `https://tesla.example.com` 37 | - **Redirect URI**: Append `/callback` to the FQDN, e.g. `https://tesla.example.com/callback` 38 | 39 | Tesla will provide a Client ID and Client Secret. Enter these in addon configuration and then Start it again. Now the `regenerate_auth` setting will be automatically disabled. 40 | 41 | Open the Web UI of this addon and click **Login to Tesla account**. After authenticating, it will redirect to your callback URL which doesn't exist, so you'll see an error like *404 not found*. That's normal. Copy the URL from that page and paste it into the text field on the Web UI, then click **Generate token from URL**. The refresh token will be printed to the log and also copied to your clipboard for later use. 42 | 43 | > Tip: The first time you request a refresh token, it will also prompt to authorize your Client ID to access your Tesla account. Allow all scopes. 44 | 45 | Using the Home Assistant iOS app, open the Addon Web UI and click **Enroll public key in your vehicle**. This should launch the Tesla app where it prompts for approval to "allow third-party access to your vehicle". If you have multiple vehicles, you'll need to do this on each of them. Your Tesla app must already be key-paired with the car. 46 | 47 | Configure the [Tesla integration](https://github.com/alandtse/tesla) to use this proxy. It should pre-fill the Client ID, URL, and certificate for you by reading them from this addon. 48 | 49 | ## Troubleshooting 50 | 51 | Enable debug mode and check the add-on logs to see what's happening. 52 | 53 | Check the [Wiki](https://github.com/llamafilm/tesla-http-proxy-addon/wiki) for common errors and solutions found by other users. 54 | 55 | Feel free to write a new Wiki page if you have been successful at setting this up in a novel way. 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------