The day my EV charger quietly got slower
A Wallbox Pulsar Plus that used to hold 32 A all night started backing off to 30 A and never recovering. Nothing in the app changed, no error was ever logged. This is how I found the exact behaviour, and narrowed the change down to a four-month window in 2025 — using nothing but the charger's own database.
The symptom
Single-phase domestic install, underground garage, Tesla Model 3, overnight charge pinned at 32 A with no solar modulation and no limit set on the car. The charger starts at 32.6 A and heats steadily. When line temperature reaches 79 °C it waits 105 seconds, applies a single 2.1 A cut, and then holds there for the rest of the session.
| Time | Temp L1 | Current L1 | Power |
|---|---|---|---|
| t+0 | 33 °C | 32.6 A | 7194 W |
| t+22m50s | 79 °C | 32.8 A | 7216 W |
| t+24m35s | 79 °C | 30.7 A | 6693 W |
| t+25m24s | 77 °C | 30.5 A | 6790 W |
| t+33m | 76 °C | 30.5 A | 6671 W |
Three properties worth writing down, because they shape how you detect this at all:
- The threshold is 79 °C with about 105 seconds of persistence. It is not an instantaneous comparator — a night that touches the threshold without holding it never triggers a cut. That single fact explained several nights I had previously filed as inconsistent.
- The cut is discrete and one-shot: −2.1 A, −523 W, in a single sample. Temperature responds within a minute and the system settles into equilibrium around 76 °C.
- Configuration never moves.
max_charging_currentandmax_avbl_currentsit at 32 A throughout, the cut included.
That last point is the trap. If you are watching the config fields — which is what the app shows you, and what the MQTT bridge exposes most prominently — you see nothing at all. The only signal is real measured current falling below what the configured maximum would allow, correlated with temperature.
Dating the change
My recollection was “it worked fine at first, then one day it started, and it stayed that way”. Recollection is not evidence, so: the charger keeps a session table going back two full years, which is far more history than the live telemetry buffer holds. Mean power per session is a crude metric — it folds in the car's own taper at high state of charge — but it is perfectly valid for comparing equivalent sessions against each other.
| Session | Mean power | |
|---|---|---|
| 2024-11-30 | 5.62 kW | |
| 2024-12-07 | 5.63 kW | |
| 2025-02-15 | 5.74 kW | last session at the old level |
| 2025-06-23 | 5.15 kW | first session at the new level |
| 2025-07-16 | 5.13 kW | |
| 2025-08-03 | 5.11 kW | |
| 2026-01-04 | 5.06 kW | |
| 2026-05-10 | 5.37 kW |
A 10 % drop, then fifteen months of dead flat. That shape matters more than the magnitude: contact resistance creeping up from thermal cycling would give you a gradual slide. A step that lands and then holds for over a year is a parameter change, not a wearing part.
What the charger logged in that window
The error table is the only one on the device with usable timestamps going that far back. Between the last good session and the first bad one, it contains exactly four entries — each one a simultaneous restart of every service on the box.
Full stack restart: blewallbox, micro2wallbox, mywallbox, ocppwallbox, wallboxsmachine, wallbox_login.
Full stack restart, preceded by error code 48, “No driver problem found”.
A second full restart, one minute after the first.
Full stack restart.
A double restart cycle inside one minute, with a driver event wedged between them, is what a remotely applied firmware update looks like from the inside.
proven The power step and the logged restarts. Both timestamped, both from the device.
inferred That the update changed the thermal threshold. I cannot verify this from the device: cached firmware packages only reach back to 2026, and nothing on the filesystem still carries a 2025 mtime. The honest status of this claim is strong correlation, no mechanism.
My working theory is that the threshold was deliberately lowered for safety — the kind of change a manufacturer ships quietly when field data starts showing units running hotter than they would like. It fits the shape of the evidence better than any failure mode I can think of, and it has an uncomfortable implication: if that is what happened, there is nothing to repair. The unit is doing exactly what it was told to do.
What it isn't
Each of these was a live hypothesis at some point, and each one cost measurements to kill. Recording the dead ends is the whole point of a field note.
- Not ambient temperature. In April, with the garage around 15 °C, the unit still reached 81—83 °C. In September, at 28.5 °C ambient, it reaches 79 °C. The device's own rise of +50 to +60 °C over ambient at 32 A dominates completely. This one genuinely surprised me — I had assumed summer was the trigger.
- Not a limit from the car.
charge_current_requestandcharge_current_request_maxboth read 30 A: the vehicle is asking for precisely what the charger offers on the control pilot, with no limit of its own. - Not a stale control pilot. This one is subtle and worth the warning. A car that cached an old pilot value looks similar — current below the configured maximum — but the signature is inverted: with a stale pilot, current rises while temperature rises. Here the cut tracks temperature. Same symptom, opposite cause, and the fix for one is useless against the other.
- Not in the error log. The device's error table contains no over-temperature code anywhere in its entire life.
What it costs
- Sessions since
- 136
- Energy delivered
- 3003 kWh
- Hours charging
- 635
- Time overhead
- ~58 h
Those 58 hours are the difference between delivering that energy at the old level and at the current one. On a typical night it is around 30 minutes — annoying, not painful. The real cost sits in the tail: on the worst night on record the cut went all the way down to 18.3 A, which turns a full charge into an all-day affair.
How to check yours
None of this is visible from the vendor app or the cloud API, so all of it came from the device itself. The Pulsar Plus runs embedded Linux and there is excellent prior work on getting a root shell and on getting its state out over MQTT. If you want to run the same measurements on your own unit, start here — credit where it is due, I wrote neither of these:
jagheterfredrik/wallbox-pwn
Bluetooth + WiFi exploit chain that leaves you a persistent root SSH shell on the charger.
→jagheterfredrik/wallbox-mqtt-bridge
A Go service that runs on the charger and publishes its full state to MQTT with Home Assistant autodiscovery. This is what feeds the second-resolution data above.
→With a shell, the interesting state lives in a local MySQL database called wallbox. Two tables carry everything used in this write-up:
-- live telemetry: temp_l1, ac_current_rms_l1, max_charging_current...
-- circular buffer, 25k rows, roughly 2.5 months
SELECT timestamp, temp_l1, ac_current_rms_l1, max_charging_current
FROM state_values ORDER BY timestamp DESC LIMIT 50;
-- session history: this one goes back years
SELECT DATE(start_time) AS day,
ROUND(energy/1000, 1) AS kwh,
ROUND(charging_time/60) AS minutes,
ROUND(energy*3.6/charging_time, 2) AS mean_kw
FROM session
WHERE HOUR(start_time) IN (23, 0) -- overnight sessions only
AND charging_time > 7200 -- longer than two hours
AND energy > 15000
ORDER BY start_time;
Gotcha that cost me a detour: in the session table, start_time and end_time are DATETIME columns, not Unix timestamps. Wrap them in FROM_UNIXTIME() out of habit and you get a column full of NULL, which looks exactly like the field never being populated. I nearly concluded there was no usable history at all.
Also worth knowing: state_values_dc.POWER_DERATING_STATUS exists as a column but that table is empty on an AC unit — the schema is shared with the DC models. There is no derating flag to read on a Pulsar Plus. You have to infer it from current versus temperature.
On the MQTT side the topics you want are wallbox_<serial>/temp_l1/state and wallbox_<serial>/charging_current_l1/state. Log both at a few seconds' resolution through a full session and the staircase draws itself. If you also run the car through TeslaMate, cross-reference charge_current_request_max from MQTT rather than the charger_pilot_current column in Postgres — the latter reported 16 A against 30 A of measured current on my car, which is physically impossible and cost me another wrong turn.
Where this stands
Unresolved, honestly. The threshold is characterised, the change is dated to a four-month window, and the mechanism behind that window is still inference. Two things are queued.
The first is boring and will probably work: forced convection. A 120 mm fan, a smart relay, a 3D-printed bracket, and an automation that spins it up above 60 °C while charging. If the unit never reaches 79 °C it never cuts, whatever threshold the firmware is carrying. I have run a couple of sessions with the lid physically open and they still hit the threshold, which is suggestive but nowhere near enough nights to call passive convection dead — moving air is a different physical regime anyway, and that is the one worth testing properly.
The second is to ask the vendor for the firmware history on the unit and see whether that window matches a deployment. Whichever way that answer goes, the interesting part is already done: the device recorded its own regression, in two separate tables, and kept it for two years. It was sitting there the whole time, behind a mismatched timestamp format and a config field that never moves.
Once the fan is in, the test is simple and the data already exists to compare against: line current holding 32.6 A for a whole session, instead of stepping down at minute twenty-three.