The second post on Above the Grid. Read the first post for context on why off-grid solar sizing starts with measurement.
In the last post I talked about why my naive sizing targets were wrong and what I’m doing to collect better data. But I glossed over the single biggest load I’m adding to my system: EV charging. And until I could separate EV load from household load in my data, none of the sizing analysis was going to be honest.
Here’s how I brought my EV charger into Home Assistant, the half-day of debugging it took, and why it’s worth the effort for anyone trying to plan an off-grid system with an EV in the picture.
The timing question
I’d been using the charger for a couple weeks before I thought seriously about integration. It was working fine through the manufacturer’s app over WiFi. What changed was I started paying attention to when I was charging.
The thing about living off-grid is that when you charge an EV, you’re making a real decision about where the energy comes from. Charge midday on a clear day and the solar array absorbs most of it. Effectively free energy, and you’re also giving the battery somewhere to dump excess production, which reduces clipping. Charge overnight and you’re pulling eight or nine kWh out of battery storage that solar has to refill the next day, on top of every other load. Same kWh total. Completely different impact on the system.
Making smart decisions about timing required three things:
- Real-time visibility into what the charger was doing.
- The ability to control it locally, not through the manufacturer’s cloud.
- Separate tracking so I could analyze “house load” and “EV load” independently.
The charger’s app gave me number one in a basic sense. Two and three required something else. That something else is OCPP (Open Charge Point Protocol), the industry standard for communicating with EV chargers at a protocol level. If your charger speaks OCPP and you run your own OCPP server, you get local control and rich telemetry.
My charger, an IYILO RA48 (convertible 48-amp, 240V), claims OCPP 1.6J support. The app confirmed it. There’s a community-maintained HACS integration for Home Assistant (lbbrhzn/ocpp) that implements the server side. In theory: twenty minutes of setup.
In practice, several attempts.
The debugging saga
Attempt one: set up the HA OCPP integration, configure the URL in the charger’s app, wait.
Nothing. Silence on both ends, except I could see in HA’s logs that the charger was hitting the correct port. It just wasn’t completing a valid WebSocket handshake. Every eleven seconds or so, the charger connected, failed silently, disconnected.
The error specifically: did not receive a valid HTTP request / EOFError: line without CRLF. That signature, when I looked it up, is the classic fingerprint of a TLS client trying to connect to a plain-text server. My URL in the charger app was wss:// (secure WebSocket) when HA was set up for plain ws://. Fixed the prefix. Saved. Waited. Same error.
Attempt two: look for a second SSL toggle somewhere in the settings.
There wasn’t one. The app had exactly three fields: OCPP Version, URL, Authorization Key. No hidden TLS flag.
Attempt three: power-cycle the charger.
The OCPP integration documentation mentions this in passing: “you may need to reboot your charger before the changes become effective.” I’d skimmed past it. Turned out to be the whole game. The charger had cached TLS state from the original wss:// configuration, and the new plain-WS setting wasn’t fully taking effect until the WebSocket stack got reset by a full power cycle. Breaker off for thirty seconds, back on.
Connected cleanly within a minute.
A quirk worth documenting
Once the charger connected, HA showed two devices. I’d configured the integration with a charge point ID and expected that to be my single device. Instead, HA created a separate device named “charger,” and that’s where all the actual telemetry lived.
This turns out to be intentional behavior of the integration. The CPID you set in HA is for the listener. When a real charger dials in, it identifies itself with its own serial in the Boot Notification, and HA creates a separate device for that. The listener-named entity stays as a placeholder.
Not a bug, but not documented in a way I found before setup. The fix is just to rename the auto-created device to something meaningful and move on. Worth flagging if you’re following the same path.
What I ended up with
Once it was working, the payoff was substantial. The integration exposes roughly thirty entities, including real-time power draw, current, voltage, cumulative energy meter, session data (start, duration, energy), status states (Available / Preparing / Charging / Finishing), and, crucially, a charge-control switch and a max-current number entity.
I built a dashboard section with the key telemetry plus a prominent charging toggle. I can now stop or start charging from HA in about a second, entirely over LAN, with no dependency on cellular service, the manufacturer’s cloud, or my vehicle’s phone app.
That last part matters more than I’d initially thought. Cell service up here is spotty. I’m on Starlink for internet, but my vehicle uses cellular to reach its own cloud, and getting the car to respond to app commands can sometimes take minutes. A local control path that doesn’t require any internet round-trip is a genuine reliability improvement.
The zero-amp problem (and why preset buttons beat sliders)
Home Assistant’s default UI treats the “max current” control as a slider from 0 to 32 amps. Fine in theory. In practice, if you accidentally drag to zero, the charger reports “no current available” to the vehicle, the vehicle terminates its charge session, and sometimes you have to physically unplug and re-plug to wake things back up.
Separately, I had a real-world need: when the microwave kicks on and I don’t want to trip the inverter on stacked loads, I want a one-tap way to drop the EV rate quickly. A slider isn’t that. You have to open a modal, drag carefully, and hope you didn’t drag too far.
The fix was to replace the slider card with three preset buttons: 6A, 12A, 20A. Tap 12A and the charger ramps within a second, the new value shows on the dashboard, done. No modal. No drag-to-zero risk. I picked those three values because 6A is the minimum that keeps the charge session alive, 20A is my current inverter-safe ceiling, and 12A is a useful middle ground for “slow down during heavy household load.”
There’s a residual issue I can’t cleanly solve: the underlying number.charger_maximum_current entity still exposes a minimum of zero because the OCPP integration hard-codes that. I can’t override it without patching the integration. So the UI pattern is my safety net (nothing on the dashboard lets you get to zero), and I’m making a mental note that if I ever write an automation that dynamically sets the charge rate (solar-surplus charging, for example), it needs to floor at 6A, not 0.
Closing the loop on data capture
The whole reason this was worth doing: I can now separate EV load from house load in my daily solar log.
Before the integration, my “daily consumption” number was the sum of everything: lights, fridge, well pump, ice maker, EV, all of it. An 11 kWh day could be “normal house operation” or “light household plus a 5 kWh EV charge,” and I had no way to tell. That ambiguity made sizing analysis pointless.
Now I log three distinct numbers:
- EV charged kWh: captured via OCPP from the charger itself.
- Total consumption: derived from solar minus net battery change.
- House-only consumption: total minus EV.
Over the first few days of running this integration, my house-only baseline has been remarkably consistent at 8-11 kWh per day. That’s meaningful because it’s what actually dictates solar sizing when I do winter analysis. EV load is an overlay on top of that baseline, and crucially, it’s a load I can time-shift to solar-peak hours. House load isn’t.
So the data now answers two distinct questions:
- Can my solar cover the house during the worst month? (The survivability question.)
- Can my solar cover the house plus EV charging on sunny days? (The optimization question.)
Without separating the two, I was trying to answer one fuzzy question and getting a fuzzy answer.
What’s next
A few things are on the horizon:
Solar-surplus charging. The IYILO supports OCPP’s Smart Charging profile, which means I can dynamically modulate max current based on available solar headroom. The logic is easy to describe and non-trivial to tune: set max current to [solar power] − [non-EV load] − [battery charge headroom], floored at 6A, rounded to an integer. I’ll need a few months of data before I trust the rule.
Tracker 2 reconnection. I have a second string of panels currently on an older MPPT that isn’t visible to HA. Once I move it to the Victron, I’ll have complete production visibility across both strings, and I’ll finally be able to quantify how much afternoon shading the old angled-frame panels are costing me on the new roof-flush panels.
Inverter upgrade. My 20-amp EV limit isn’t a charger constraint, it’s me protecting my Schneider XW+ from load-stack faults. Eventually I’ll move to dual Victron MultiPlus II 5000VA units, at which point the EV can run at 32A or 48A and the solar-surplus logic becomes much more valuable.
But the foundation is in place: continuous data capture, local control, clean separation of loads. That’s the platform everything else gets built on.
Next time I’ll dig into what the first real weeks of clean data are showing, including one unclipped April day that generated 110% of its combined house-and-EV load, and what that does (and doesn’t) tell me about winter sizing.