diff --git a/deploy/tesla/callback.html b/deploy/tesla/callback.html new file mode 100644 index 0000000..5d20559 --- /dev/null +++ b/deploy/tesla/callback.html @@ -0,0 +1,30 @@ + + + + + + + EMS – Tesla OAuth callback + + + +
+

Tesla OAuth callback

+

Autorizační kód (zkopíruj do EMS kroku výměny tokenu):

+ +

Stránka je statická, kód se nikam neodesílá. Po výměně za + refresh token už není potřeba.

+
+ + + diff --git a/deploy/tesla/setup_tesla_domain.sh b/deploy/tesla/setup_tesla_domain.sh new file mode 100755 index 0000000..607b0c4 --- /dev/null +++ b/deploy/tesla/setup_tesla_domain.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Jednorázová příprava domény ems.vojacek.eu pro Tesla Fleet API. +# SPOUŠTĚT NA SERVERU (tam, kde běží hostovský Caddy): bash setup_tesla_domain.sh +# +# Co udělá: +# 1. vygeneruje EC keypair (prime256v1) pro Tesla Fleet API +# - privátní klíč: /opt/ems-deploy/secrets/tesla-fleet-private.pem (0600) +# - veřejný klíč: /opt/ems-deploy/public/.well-known/appspecific/com.tesla.3p.public-key.pem +# 2. nakopíruje statickou OAuth callback stránku +# 3. vypíše Caddy blok k přidání do Caddyfile + reload příkaz +# +# Veřejně vystavené je POUZE: public key (.pem) a callback.html. Vše ostatní 404. +# EMS aplikace zůstává na VPN. + +set -euo pipefail + +ROOT="${EMS_DEPLOY_ROOT:-/opt/ems-deploy}" +PUB="$ROOT/public" +SEC="$ROOT/secrets" +WELLKNOWN="$PUB/.well-known/appspecific" +PRIV_KEY="$SEC/tesla-fleet-private.pem" +PUB_KEY="$WELLKNOWN/com.tesla.3p.public-key.pem" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +mkdir -p "$WELLKNOWN" "$SEC" "$PUB/tesla" +chmod 700 "$SEC" + +if [[ -f "$PRIV_KEY" ]]; then + echo "Privátní klíč už existuje: $PRIV_KEY (negeneruji znovu)" +else + openssl ecparam -name prime256v1 -genkey -noout -out "$PRIV_KEY" + chmod 600 "$PRIV_KEY" + echo "Vygenerován privátní klíč: $PRIV_KEY" +fi + +openssl ec -in "$PRIV_KEY" -pubout -out "$PUB_KEY" 2>/dev/null +echo "Veřejný klíč: $PUB_KEY" + +install -m 0644 "$SCRIPT_DIR/callback.html" "$PUB/tesla/callback.html" +echo "Callback stránka: $PUB/tesla/callback.html" + +cat <<'CADDY' + +================================================================ +Přidej do /etc/caddy/Caddyfile (a pak: systemctl reload caddy): +================================================================ + +ems.vojacek.eu { + root * /opt/ems-deploy/public + + # Tesla Fleet API: veřejný klíč pro partner account registraci + handle /.well-known/appspecific/com.tesla.3p.public-key.pem { + file_server + } + + # Jednorázový OAuth callback (statická stránka zobrazí ?code=) + handle /tesla/callback* { + file_server + } + + # Nic dalšího neexponovat — EMS žije jen na VPN + handle { + respond 404 + } +} + +================================================================ +Ověření (z venku): + curl -sI https://ems.vojacek.eu/.well-known/appspecific/com.tesla.3p.public-key.pem + -> 200 + platný Let's Encrypt certifikát + curl -sI https://ems.vojacek.eu/cokoliv -> 404 +================================================================ +CADDY diff --git a/docs/tesla-fleet-api.md b/docs/tesla-fleet-api.md new file mode 100644 index 0000000..2bd7afe --- /dev/null +++ b/docs/tesla-fleet-api.md @@ -0,0 +1,88 @@ +# Tesla Fleet API — napojení EMS (čtení SoC vozidla) + +Cíl: po příjezdu EV přečíst skutečné SoC → `ev_session.energy_needed_wh` +přesně místo defaultu. Doména `ems.vojacek.eu` slouží JEN jako veřejná +vizitka pro Tesla (cert + public key + jednorázový OAuth callback) — EMS +samotné zůstává na VPN. + +## 1. Doména (jednorázově, na serveru) + +```bash +bash /opt/ems-deploy/app/deploy/tesla/setup_tesla_domain.sh +# → přidat vypsaný blok do /etc/caddy/Caddyfile a `systemctl reload caddy` +``` + +Veřejné je pouze: +- `https://ems.vojacek.eu/.well-known/appspecific/com.tesla.3p.public-key.pem` +- `https://ems.vojacek.eu/tesla/callback` (statická stránka zobrazí `?code=`) +- vše ostatní → 404; certifikát řeší Caddy (Let's Encrypt) automaticky. + +## 2. Tesla developer portál (developer.tesla.com) + +Vytvořit aplikaci: +- **Allowed Origin:** `https://ems.vojacek.eu` +- **Allowed Redirect URI:** `https://ems.vojacek.eu/tesla/callback` +- **Scopes:** `openid offline_access vehicle_device_data` (čtení SoC stačí; + `vehicle_charging_cmds` až kdybychom chtěli vozidlu poroučet my — teď řídí + wallbox, ne auto) + +→ získáš `CLIENT_ID` a `CLIENT_SECRET`. + +## 3. Partner account registrace (jednorázově) + +```bash +# partner token (client_credentials): +curl -s https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token \ + -d grant_type=client_credentials -d client_id=$CLIENT_ID \ + -d client_secret=$CLIENT_SECRET \ + -d scope='openid vehicle_device_data' \ + -d audience=https://fleet-api.prd.eu.vn.cloud.tesla.com | jq -r .access_token + +# registrace domény (ověří si public key na .well-known): +curl -s -X POST https://fleet-api.prd.eu.vn.cloud.tesla.com/api/1/partner_accounts \ + -H "Authorization: Bearer $PARTNER_TOKEN" -H 'Content-Type: application/json' \ + -d '{"domain":"ems.vojacek.eu"}' +``` + +## 4. Uživatelský OAuth (jednorázově, z prohlížeče) + +``` +https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/authorize? + response_type=code&client_id=$CLIENT_ID& + redirect_uri=https://ems.vojacek.eu/tesla/callback& + scope=openid%20offline_access%20vehicle_device_data&state=ems +``` +Přihlásíš se Tesla účtem → redirect na callback stránku → zkopíruješ `code` → +výměna za tokeny: + +```bash +curl -s https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token \ + -d grant_type=authorization_code -d client_id=$CLIENT_ID \ + -d client_secret=$CLIENT_SECRET -d code=$CODE \ + -d redirect_uri=https://ems.vojacek.eu/tesla/callback +# → access_token (krátký) + refresh_token (ULOŽIT — viz krok 5) +``` + +## 5. EMS integrace (plán implementace) + +- env: `TESLA_CLIENT_ID`, `TESLA_CLIENT_SECRET`, `TESLA_REFRESH_TOKEN` + (`/opt/ems-deploy/.env`); refresh token rotuje → po každém refreshi uložit + nový (tabulka `ems.tesla_token` nebo aktualizace .env jobem — rozhodnout). +- `services/tesla_client.py`: refresh → access token (cache ~8 h), + `GET /api/1/vehicles`, `GET /api/1/vehicles/{id}/vehicle_data` + → `charge_state.battery_level`, `charge_state.charge_limit_soc`. +- Hook v `_on_ev_arrival` (telemetry_collector): po detekci příjezdu zavolat + API, `energy_needed_wh = max(0, (limit − level)/100 × battery_capacity_kwh + × 1000 / účinnost nabíjení)` → `fn_ev_session_apply_patch`; replan už běží. +- Mapování vozidlo↔wallbox: `asset_vehicle.api_type='tesla'` + VIN sloupec + (doplnit migrací), heuristika „Tesla je na chargeru X" dle + `default_charger_id`. +- Pozn.: vehicle_data budí auto (vampire drain) — volat jen při příjezdu + a max 1× za session. + +## Stav + +- [x] skript + callback + dokumentace v repu +- [ ] uživatel: spustit setup na serveru + Caddy blok + reload +- [ ] uživatel: developer portál (CLIENT_ID/SECRET) + partner registrace + OAuth +- [ ] EMS: tesla_client.py + hook (čeká na credentials)