How I Finally Automated My Tesla Charging (With an AI Partner)
A story about Home Assistant frustration, NEM 3.0 math, three orphan crypto keys, and what it actually feels like to build something with AI
I’ve had a Tesla Model Y and a Powerwall for over a year. I knew I was probably leaving money on the table with my charging schedule. PG&E’s peak rates hit $0.38/kWh between 4–9 PM. My solar exports back at $0.08/kWh under NEM 3.0. The math is brutal — every kWh I self-consume is worth 4.75x more than one I sell back to the grid.
I knew what I wanted: a system that would charge the car at the right time, based on solar availability, grid prices, and battery state. Not a fixed schedule. An actual smart system.
I just couldn’t build it.
The Home Assistant Rabbit Hole
Everyone in the Tesla/solar community points to Home Assistant. It’s open source, wildly flexible, has integrations for everything. On paper, it’s exactly what I needed.
In practice, it nearly broke me.
The documentation is sparse. Not “sparse” in the way that means you have to read carefully — sparse in the way that means critical steps are buried in three-year-old forum threads, half the integrations are community-maintained and outdated, and the error messages tell you nothing useful. I spent hours trying to get the Tesla integration working. I’d get partway through a setup, hit an undocumented wall, find a workaround on Reddit, try it, get a different error.
The frustrating part wasn’t that it was hard. It was that I couldn’t tell if I was one step away from it working or fundamentally doing it wrong. There was no one to ask. The docs assumed knowledge I didn’t have, and the community answers were scattered across years of forum posts that may or may not still apply.
I gave up. Not because I’m not technical enough — I’ve been hacking with technology for years. I gave up because the feedback loop was broken and I had no co-pilot.
Starting Over, Differently
A few months later, I came back to the problem — this time with Nattu (my AI assistant running on OpenClaw) as a full collaborator.
The difference was immediate. Instead of hunting through docs alone, I could say: “Here’s what I’m trying to do. Here’s what I know about the Tesla API. Help me figure out the right approach.” And get back not just an answer, but a reasoned one with trade-offs explained.
I came in with a wish list a mile long: never charge during peak, prefer solar window on weekday afternoons, charge overnight on weekdays at the off-peak rate, opportunistically grab solar on weekends, maintain a battery floor, handle VPP dispatch events, defer to manual overrides. Every rule felt obvious in isolation.
I built all of them.
That was the first mistake.
Building the Actual System
The stack we landed on:
Tesla Fleet API for reads — battery %, solar production, home load, Powerwall state, grid flow
tesla-control binary for signed vehicle commands (start, stop, set amps)
A virtual key paired to the car via my own domain (ev.srinatar.xyz) — Tesla’s new required auth model for any third-party command
charger.py— Python script holding the decision logiclaunchd running it every 30 minutes as a background service on my Mac mini
Let me tell you about the virtual key.
Tesla’s newer API requires you to generate an ECDSA keypair, host the public key at a very specific path (/.well-known/appspecific/com.tesla.3p.public-key.pem) on a domain you control, register that domain with Tesla via their partner API, and then pair the key to the car from the Tesla app via a specially-crafted URL. The car then asks you to approve the pairing on the touchscreen. Once paired, every vehicle command has to be cryptographically signed by your private key before Tesla will accept it.
This is a good security model. It is also a security model that gives you many opportunities to footgun yourself.
Here’s where I ended up at one point: three different public keys on disk, no idea which one was paired with the car, an nginx serving one of them publicly but no matching private key anywhere on the system, and a Cloudflare tunnel that — I would later discover — didn’t even have a public hostname route configured, so the public URL where Tesla expected to fetch my key was timing out from the outside world.
Tesla’s documentation does not warn you about any of this.
Nattu helped me untangle it: ran a hash on every .pem file in my home directory, cross-referenced which key matched the one nginx was serving, figured out the Cloudflare tunnel was misconfigured, walked me through generating a fresh keypair from scratch, hosting it, registering it with Tesla, and pairing it. End-to-end signed command working: tesla-control charging-set-amps 14 → car responds, amps change, exit code 0.
That was the high point of the project — not because automated charging is technically impressive, but because the kind of debugging it required (a six-step distributed system spanning my Mac, nginx, Cloudflare, Tesla’s servers, and the car itself) is exactly the kind of thing I would have given up on, alone, like I gave up on Home Assistant.
The Less-Is-More Lesson
While the auth was being wrangled, the decision logic was running. And it kept doing things I didn’t want.
I’d manually start a charge at 2 PM because I knew solar was abundant. Thirty minutes later, charger.py would stop it because some “insufficient solar/PW headroom” rule fired. I’d start it again. It would stop it again. The car got fewer kWh than if the system hadn’t existed at all.
The smart system was actively making my life worse.
The fix was uncomfortable: we ripped out almost every rule. The current charger.py does exactly one proactive thing — it stops charging during peak hours (4–9 PM). All other times, it stays out of the way. If I want to charge, I charge. If I’m running a special solar-only week before a road trip, that’s a separate explicit override. When the peak-hour stop fires, it sends me a Telegram notification so I’m never surprised.
The lesson was clear and slightly humbling: the bottleneck wasn’t smarter logic. The bottleneck was a clean line between “what the system decides” and “what I decide.” Once those stopped fighting each other, everything worked.
The 30-minute launchd job still fires. Most of the time it logs the state, looks around, and does nothing. That’s the system working correctly.
What Building With AI Actually Felt Like
This is the part I want you to take-away from this post.
I’ve used AI tools for a long time. I use them to write, to research, to summarize. That’s using AI. This was different — this was building with AI.
The difference is that Nattu held context across the entire project. When I hit a problem with the Fleet API auth, I didn’t have to re-explain the whole setup. When the over-engineered first version was misbehaving, Nattu remembered the NEM 3.0 math and pushed back when I tried to bolt on more rules instead of removing them. When I got frustrated chasing the three-orphan-keys mystery and wanted to take a shortcut, I got an honest read on why that shortcut would leave me worse off.
It felt less like querying a tool and more like working with someone who was actually invested in getting it right.
The Home Assistant failure wasn’t really about the software. It was about trying to navigate complexity alone, without a feedback loop, without someone to reason through it with. That’s what was missing.
What’s Next
The simple system works. Now that signed vehicle commands are unblocked, the next step is the version I actually wanted from the start: adaptive amp control. Read real-time solar output and home load every 30 minutes. Compute the surplus. Set the car’s charge amps to match — pause if surplus is tiny, ramp up to 18A when solar is pouring in. Manage the Powerwall so it doesn’t hit 100% before peak hours, preserving headroom to absorb the late-afternoon solar that would otherwise export at $0.08/kWh.
The economics are simple: turning a $0.08 export into a $0.36 self-consumed kWh, every kWh, every day the sun shines. Over a year, that’s real money.
But this time I’m building it with the lesson freshly learned: one rule at a time, each one earning its keep, with a kill switch in plain sight.
The deeper lesson I keep coming back to: the bottleneck to building useful things with AI isn’t intelligence. It’s the quality of collaboration. The projects where I’ve made real progress are the ones where I brought a real problem, stayed in the conversation, pushed back when something didn’t feel right, and was willing to delete code that wasn’t earning its complexity.
That’s not a new skill. That’s just how good work gets done — with a partner who’s paying attention.
If you’re trying to do something similar with Tesla + solar, I’m happy to share the scripts. Find me on X at @srinatar.
