
Plan Big Pickle----Own plan
To understand this blog post you need to read the post "Keeeping token cost down and evaluation test" and the other posts
Tesla Cheap-Hour Charger — Implementation Plan
Overview
A single-container Docker app that controls Tesla charging during the cheapest hourly electricity prices (DK1, Denmark). Minimal API usage to stay within the $10/month Tesla Fleet API credit.
Architecture
┌─────────────────────────────────────────┐
│ Docker Container │
│ ┌───────────┐ ┌────────────────┐ │
│ │ Express │ │ VCP (Go) │ │
│ │ ─ auth │ │ signs cmds │ │
│ │ ─ routes │ └──────┬─────────┘ │
│ │ ─ cron │ │ │
│ │ ─ UI │ │ │
│ └─────┬─────┘ │ │
│ │ │ │
│ ┌────▼─────────────────▼────┐ │
│ │ /app/data/ │ │
│ │ ├── config.json │ │
│ │ ├── ec_private_key.pem │ │
│ │ └── price_cache.json │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────┘
Stack
- Runtime: Node.js + TypeScript + Express 5
- UI: htmx + Tailwind CSS (served directly from Express, no nginx)
- Charts: Chart.js via CDN (24h price bar chart)
- Config: JSON file on mounted volume
- VCP: teslamotors/vehicle-command built from Go, bundled in-container
- Container: Multi-stage Dockerfile (Go build stage → Node runtime stage)
Daily Cycle
| Time | Action | Tesla API calls | Cost |
|---|---|---|---|
| 14:00 CET | Fetch Energinet DK1 prices (next 24-36h) | 0 | $0 |
| monitor_start (e.g. 18:00) | vehicle_data?endpoints=charge_state | 1 Data ($0.002) | $0.002 |
| If plugged in → compute cheapest window | — | — | |
| If not plugged in → re-check every 15 min | ~4-8 Data | ~$0.008-0.016 | |
| Window start | wake_up (if asleep) + charge_start | 1 Wake ($0.02) + 1 Cmd ($0.001) | $0.021 |
| Window end | charge_stop | 1 Cmd ($0.001) | $0.001 |
| Total (best case) | $0.024/night | ||
| Total (worst case) | ~$0.04/night | ||
| Monthly (30 days) | $0.72-$1.20 |
Schedule Algorithm
- Fetch hourly spot prices from Energi Data Service (DK1)
- Add Danish tariffs (Konstant model):
- 00:00-06:00: 0.20 DKK
- 06:00-17:00: 0.45 DKK
- 17:00-21:00: 1.10 DKK
- 21:00-24:00: 0.45 DKK
- Get current_soc from vehicle_data
- kwh_needed = (target_soc - current_soc) / 100 * battery_kwh
- hours_needed = ceil(kwh_needed / charger_kw)
- Filter hours: [now, departure_time] excluding past hours
- Find cheapest contiguous window of hours_needed length
- If no suitable window exists → skip night (log reason)
Auth & Setup
Reuses the same OAuth PKCE flow as the existing Tesla Charger app:
-
In-app setup wizard (served at /setup):
- Enter Tesla Developer client_id + client_secret
- Click "Connect to Tesla" → redirected to Tesla authorize URL
- User logs in on Tesla, grants permissions
- Tesla redirects to /api/auth/callback with auth code
- App exchanges code for tokens (access + refresh), stores encrypted in config.json
- App calls partner_accounts to register as Fleet API partner
- App generates EC P-256 key pair, serves public key
- VCP proxy starts with private key for command signing
-
Token refresh: Auto-refresh 5 min before expiry. If refresh fails, re-auth required.
Config Fields (config.json)
| Field | Type | Default | Editable via UI |
|---|---|---|---|
| target_soc | number | 80 | Yes (slider 50-100) |
| departure_time | string | "07:00" | Yes (time picker) |
| monitor_start | string | "18:00" | Yes (time picker) |
| charger_kw | number | 11 | Yes (Settings page) |
| battery_kwh | number | 75 | Yes (Settings page) |
| active_days | bitmask | 62 (Mon-Fri) | Yes (checkboxes) |
| tesla_vin | string | — | Auto-detected |
| ntfy_topic | string | "" | Yes (Settings page) |
| tesla_client_id | string | — | Setup wizard |
| tesla_client_secret | string | — | Setup wizard (encrypted) |
| access_token | string | — | OAuth flow (encrypted) |
| refresh_token | string | — | OAuth flow (encrypted) |
| ec_private_key | string | — | Generated (encrypted) |
| ec_public_key | string | — | Generated |
UI Pages
Dashboard (/)
- Large battery SOC percentage display
- Charging status badge (Idle / Charging / Disconnected / Done / Planned)
- Next planned charging window: "Charging 03:00-05:00"
- Target SOC slider
- Departure time picker
- Monitor start time picker
Prices (/prices)
- 24h bar chart via Chart.js
- Cheapest hours highlighted in green
- Planned charging window highlighted in blue
- Current price indicator
Settings (/settings)
- Charger power input (kW)
- Battery capacity input (kWh)
- Active days checkboxes (Mon-Sun)
- ntfy.sh topic input (optional)
Setup (/setup)
- 3-step OAuth wizard (hidden once setup is complete)
- Step 1: Enter Developer credentials
- Step 2: Redirect to Tesla for authorization
- Step 3: Confirm success, show VIN
API Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/config | Yes | Read config |
| POST | /api/config | Yes | Update config |
| GET | /api/status | Yes | Vehicle SOC, charging state, schedule |
| GET | /api/prices | Yes | Today + tomorrow prices |
| POST | /api/prices/refresh | Yes | Force price refresh |
| GET | /api/schedule | Yes | Planned charging window |
| GET | /api/auth/status | No | Tesla auth status |
| GET | /api/auth/start | No | Initiate OAuth flow |
| GET | /api/auth/callback | No | OAuth callback |
| POST | /api/auth/register | Yes | Register partner account |
| GET | /health | No | Container health |
| GET | /.well-known/appspecific/com.tesla.3p.public-key.pem | No | Tesla public key |
Error Handling
| Scenario | Behavior |
|---|---|
| Car already at/above target SOC | Skip night. Log: "Already charged." |
| Not plugged in at check | Re-check every 15 min. If still unplugged 2h before departure, abort. |
| Price API (Energinet) down | Use last cached prices. If no cache, skip night. |
| Tesla 429 rate limit | Exponential backoff: 1min -> 5min -> 15min -> abort night. |
| charge_start fails | Retry once after 30s. If still fails, log + ntfy + abort. |
| charge_stop fails | Retry once after 30s. Log + ntfy. Car stops on its own at target SOC. |
| Container restart mid-charge | On boot: check vehicle_data. If charging and past window, stop. |
| Token refresh fails | Clear tokens. Re-auth required flag in UI. |
| VCP proxy fails | Restart VCP. If fails twice, abort. |
Project Structure
tesla-cheap-charger/
├── Dockerfile # Multi-stage build (Go VCP + Node)
├── docker-compose.yml # Single service
├── .env.example # Env vars template
├── .gitignore
├── src/
│ ├── index.ts # Express entry point
│ ├── config.ts # JSON config reader/writer
│ ├── scheduler.ts # Cron/setInterval schedule manager
│ ├── tesla/
│ │ ├── client.ts # Fleet API + VCP proxy caller
│ │ ├── auth.ts # OAuth PKCE flow
│ │ └── keys.ts # EC P-256 key management
│ ├── prices/
│ │ ├── energinet.ts # Energi Data Service client (DK1)
│ │ └── tariffs.ts # Konstant tariff model
│ ├── charging/
│ │ ├── optimizer.ts # Cheapest contiguous window algorithm
│ │ └── executor.ts # Start/stop charge loop
│ ├── routes/
│ │ ├── api.ts # REST API routes
│ │ ├── auth.ts # OAuth callback routes
│ │ └── pages.ts # HTML page routes (htmx)
│ ├── views/
│ │ ├── layout.html # Base layout (Tailwind)
│ │ ├── dashboard.html # Main dashboard
│ │ ├── prices.html # Price chart page
│ │ ├── settings.html # Settings page
│ │ └── setup.html # OAuth setup wizard
│ ├── notifications/
│ │ └── ntfy.ts # Push notification sender
│ └── utils/
│ ├── time.ts # Denmark timezone helpers
│ └── crypto.ts # AES encryption for tokens
├── package.json
├── tsconfig.json
└── data/ # Mounted volume (gitignored)
├── config.json
├── ec_private_key.pem
└── price_cache.json
Docker Compose
yamlservices: app: build: . ports: - "3001:3001" volumes: - ./data:/app/data environment: - PORT=3001 - TZ=Europe/Copenhagen - NODE_ENV=production restart: unless-stopped
Multi-stage Dockerfile:
- Stage 1 (golang): Clone teslamotors/vehicle-command, build tesla-http-proxy
- Stage 2 (node:22-alpine): Copy Go binary, install npm deps, copy source
- Runtime: VCP runs as child process on port 4040, Express on port 3001
Budget
| Item | Monthly | Notes |
|---|---|---|
| Tesla Fleet API | ~$0.72-1.20 | Covered by $10 monthly credit |
| VPS hosting | $0 | Docker on existing home server |
| Energinet API | $0 | Free, open data |
| ntfy.sh | $0 | Free, open source |
| Total out-of-pocket | $0 |
Dependencies
- express — HTTP framework
- axios — HTTP client (Tesla, Energinet, ntfy)
- node-cron — In-process scheduling
- crypto-js — AES token encryption
- uuid — OAuth state nonce
- dotenv — Env file loading
- Dev: typescript, tsx, @types/*
- No native modules needed (pure JS/TS)
Share this post
Related Posts

Plan MAI----Own plan
To understand this blog post you need to read the post "Keeeping token cost down and evaluation test" and the other posts
July 4, 2026

Plan Kimi K2.7-coder----Own plan
To understand this blog post you need to read the post "Keeeping token cost down and evaluation test" and the other posts
July 4, 2026

Keeping token cost down and evaluation test
I asked tree different LLMs and AI "coder appa" to writhe a plan for a Tesla Charging app, with the goal of not using the token limit agains the Tesla APi (10$) free pr month. and to make the none High-end LLMs to make the plan. and i used /grill-me for all of them
July 4, 2026
Comments
Be the first to leave a comment.