Tesla Fleet API: příprava domény ems.vojacek.eu (cert + public key + callback)
Doména slouží jen jako veřejná vizitka pro Tesla: Caddy blok vystavuje POUZE .well-known public key a statickou OAuth callback stránku (zobrazí ?code=, nic neodesílá), vše ostatní 404 — EMS zůstává na VPN. Certifikát Let's Encrypt řeší hostovský Caddy automaticky. deploy/tesla/setup_tesla_domain.sh (spustit NA SERVERU): EC keypair prime256v1 (privátní do /opt/ems-deploy/secrets 0600), public do .well-known, callback, vypíše Caddy blok. docs/tesla-fleet-api.md: developer portál, partner registrace, OAuth flow, plán EMS integrace (tesla_client + hook v _on_ev_arrival). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
30
deploy/tesla/callback.html
Normal file
30
deploy/tesla/callback.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="cs">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>EMS – Tesla OAuth callback</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0;
|
||||||
|
display: grid; place-items: center; min-height: 100vh; margin: 0; }
|
||||||
|
.card { background: #1e293b; padding: 2rem; border-radius: 12px; max-width: 640px; }
|
||||||
|
code { background: #0f172a; padding: .5rem; border-radius: 6px; display: block;
|
||||||
|
word-break: break-all; margin-top: .5rem; user-select: all; }
|
||||||
|
.muted { color: #94a3b8; font-size: .9rem; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h2>Tesla OAuth callback</h2>
|
||||||
|
<p>Autorizační kód (zkopíruj do EMS kroku výměny tokenu):</p>
|
||||||
|
<code id="code">—</code>
|
||||||
|
<p class="muted">Stránka je statická, kód se nikam neodesílá. Po výměně za
|
||||||
|
refresh token už není potřeba.</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const p = new URLSearchParams(location.search);
|
||||||
|
document.getElementById("code").textContent = p.get("code") || "(chybí ?code= v URL)";
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
73
deploy/tesla/setup_tesla_domain.sh
Executable file
73
deploy/tesla/setup_tesla_domain.sh
Executable file
@@ -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
|
||||||
88
docs/tesla-fleet-api.md
Normal file
88
docs/tesla-fleet-api.md
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user