Build & operations documentationv2.50

Charging on solar surplus,
at the hour the grid is congested

A Fronius Symo GEN24 Plus, a Tesla Wall Connector Gen 3, a Model 3 and a Raspberry Pi 4, tied together with evcc so the car soaks up the array's midday peak on site — and holds the voltage at the feed-in point below the level at which the installation starts protecting itself.

Array
7 kWthree-phase
Inverter
6.0 kW ACSymo GEN24 Plus
Controller
Raspberry Pi 44–7 W, wired
Control path
evcc + BLEno internet needed
History
InfluxDB 2.9Grafana 13.1
The installation at work. Model 3 on the driveway, cable to the Gen 3 Wall Connector, distribution board.
Credits
SC Electrica SRL Arad & Timișoara · since 1993

The electrical installation — and the reason everything above it works.

Thirty years in business, seventy-plus specialists, ANRE-authorised (ANRE is Romania's national energy regulator; only electricians it licenses may work on a grid connection), a Fronius partner, and more than a thousand completed installations — and it shows in the part nobody photographs. Software can only ever be as good as the installation underneath it.

If you are from Arad or Timiș and you are thinking about photovoltaics or an EV charger — electrica-arad.ro. Ask for the full job. They are worth it.

evcc open source · MIT

The engine. Every control decision in this build — when to start, how many amps to pull, when a cloud has lasted long enough to stop — is evcc's. It speaks Modbus to the Fronius, BLE to the car and HTTP to the charger, it runs entirely on the local network with nothing off-site, and it supports hundreds of devices because a genuinely active community keeps adding them. It is the rare open-source project that is not merely published but alive.

→ Sponsor evcc

Mihai Gafițeanu

build, tuning & the calls that mattered

Claude

Anthropic · claude-opus-5, claude-fable-5

Section 01Dashboard in action

The result first. Three screenshots of the live board, taken on 27 August 2026 — the Pi’s health at the top, power in the middle, charging at the bottom. The rest of the page explains how to build it.

The health half of the board: gauges, maintenance log, version checks and a day of system graphs
The Pi’s half. Gauges for heat, processor, memory and disks; the right rail repeats them over 24 hours. In the middle: the maintenance buttons, the log of the last window — 5 archives, 86.4 MB, pushed and proven on the share, then a reboot with all four ports answering 41 seconds later — and every version up to date. The car tiles read 43%, BLE proxy UP, signal at −78 dB.
Power flow diagram with today's totals, 14-day bars, phase voltages and the charging history bands
Power, live. At capture time the roof made 6.2 kW: 0.6 kW ran the house, 2.3 kW charged the car on solar alone, and 3.3 kW flowed out to the grid. Under the diagram: today’s totals, fourteen days of production and charging, and the three phases’ voltages and currents.
Charging graphs: energy per session, state of charge, house voltage per phase and per-phase volts and amps
Charging, in detail. The day’s sessions: energy charged, the car’s state of charge stepping up, and the house voltage on each phase against its limit lines. The three bottom panels follow each phase’s volts and amps while the car charges.

Section 02The reasoning

On a clear day the whole street exports at once. Voltage at the feed-in point climbs, and above a certain level the installation starts defending itself — by throwing away production, at precisely the hour the array makes the most of it.

Why any of this exists

One household’s reasons, in the order they actually arrived. Everything after this section is the argument they led to, and everything after that is the build.

I had the panels first. Three years ago I chose Fronius over Huawei and the other Chinese brands deliberately, and it was not a price decision: I was not willing to hand the controls of my own generation to a state that behaves like an adversary rather than a partner. An Austrian company, European engineering, inside the European Union. It cost more. I would choose it again.

Then I had the overvoltage problem — arriving at my own feed-in point rather than in a regulator’s consultation document.

And I have a hunch that 2026 slowly turns into another 1979. So I did the arithmetic instead of worrying about it. A new Tesla Model 3, charged on as much of my own surplus as I can get into it, costs less to run over its life than a ten-year-old 2.0-litre diesel — not because the car is cheap, but because the fuel is already on the roof and it is already paid for.

So I bought the car, installed evcc, and everything else followed from that one step. Configuring evcc made it plain that I could not make an informed decision about any of it without knowing the numbers, so the InfluxDB integration went in next. Then the Grafana integration, and the dashboard began. And once there were software packages running on a Pi in my own house, I had to have monitoring, backup, restore and update mechanisms.

That chain — panels, overvoltage, a feeling, a car, then everything the car dragged in behind it — is the entire project.

A prosumer’s case · Energy · Romania I have the right to fight. My premises, my yard, my house, my Wi‑Fi, my electricity. Romania added a third of a million rooftop prosumers and 3,789 MW of capacity in three years, on a network nobody rebuilt to carry it. The case for fixing the wires rather than the rooftops — and one household’s reasons for not waiting to find out whether anyone will.
253 V

The prosumer build-out

Romania asked households to put solar on their roofs, and in under three years a third of a million of them did. The wires underneath were not rebuilt to carry it.

The state's invitation was explicit and it worked. Registered prosumers — households and small businesses that both draw from the grid and export to it — went from roughly a hundred thousand at the end of 2023 to 332,684 at the end of April 2026, and the rate has not slowed: more than 10,600 new connections in that single month, the fastest-growing prosumer population in Europe. Almost a third of them now have batteries as well.

Registered prosumers and installed capacity (ANRE)
DateProsumersCapacityBasis
December 2023~100,0001,400 MWapproximate
October 2024~160,0002,000 MWapproximate
December 2025294,0003,400 MWregister
April 2026332,6843,789 MWregister, 30 April
Registered prosumers, four dates Dec 2023 — 100,000 ~100,000 Dec 2023 Oct 2024 — 160,000 ~160,000 Oct 2024 Dec 2025 — 294,000 294,000 Dec 2025 Apr 2026 — 332,684 332,684 Apr 2026
Four dates, one direction. The first two counts are approximate and are marked with a tilde; the last is the exact figure from the register at 30 April 2026.

3,789 MW is the equivalent of more than five Cernavodă reactors, and it was assembled without a single tender, by families buying panels a roof at a time.

253 V

The subsidy, and its withdrawal

Casa Verde Fotovoltaice was the state's own programme, and it is the reason a hundred thousand of these roofs exist.

Run by the Environment Fund Administration since 2019, the scheme offered households a non-refundable grant of up to 20,000 lei — roughly €4,000 — toward a complete rooftop system. With the grant, an installation paid for itself in under two years rather than five or six. That is the whole mechanism, and on paper it is a good one.

The execution was less good. In its early years the AFM held installations up for two full years and definitively rejected almost 9,000 applicants, most of them for reasons that were the agency's own. Later sessions ran on a fixed budget, a limited number of places and a window that closed within hours, on a platform that fell over while it was open. Applicants kept coming anyway. Something over 100,000 households got their panels through the scheme; several times that number stopped waiting and paid in full themselves.

Then the programme was dropped from the AFM's 2026 budget, its €286 million redirected, with roughly €76 million left behind for battery storage. The invitation was withdrawn. The installed base it created did not go anywhere.

253 V

Overvoltage, and who is asked to absorb it

The regulator's own account of the problem points at the network. The remedy on the table points at the rooftops.

When a street exports at once, voltage at each feed-in point rises, and above a threshold every inverter on it begins to curtail and then disconnect. ANRE does not dispute where that comes from: the overvoltage that shuts inverters down is attributed to ageing and undersized networks, badly set transformers, and a grid that was never adapted to two-way flow. The consequence, however, lands on the prosumer, who is disconnected — in some places several times a day — and warned against touching the inverter's settings.

The remedy most often proposed is not a better and smarter grid. It is a legal right for distributors to reach into privately owned inverters and switch them off remotely, as a grid-balancing tool, alongside the idea of capping household connections at 5 kW. Equipment bought, installed and paid for by the household, with the off-switch held elsewhere.

Installed prosumer capacity in megawatts, four dates Dec 2023 — 1,400 MW 1,400 MW Dec 2023 Oct 2024 — 2,000 MW 2,000 MW Oct 2024 Dec 2025 — 3,400 MW 3,400 MW Dec 2025 Apr 2026 — 3,789 MW 3,789 MW Apr 2026
What was built while the question was being debated. Installed prosumer capacity, ANRE figures. By the end of 2025 some 66,000 of these installations also had batteries — around 2,000 MW of storage, again very largely at the owners' expense.
Where this document joins the argument

253 V is the number the second half of this page is built around. It is the level at which the inverter curtails and stops, and every design decision exists to keep the feed-in point below it without anyone being disconnected. The argument and the build are about the same four volts.

253 V

The Spanish blackout

When the Iberian peninsula went dark, the first explanation offered nearly everywhere was solar. The investigations found otherwise: renewables played no role in causing the blackout. The failures identified were a voltage miscalculation by the grid operator and conventional plants that did not perform the voltage-control duty they were there to perform. Solar generation was part of how supply was restored.

Blame the panels, investigate, find the fault in the wires.

253 V

The Dutch response

The Netherlands reached this wall first, with a worse version of the same problem, and answered it by expanding the network rather than by reaching into anyone's equipment.

A third of Dutch homes have panels — the European record — on a grid designed around a handful of large gas plants. The response was a National Grid Congestion Action Plan: change the law so that grid-expansion permits are granted faster, secure land for network extensions in advance, and declare those extensions of important social interest so that licensing takes a year and a half less. Behind it sits network investment heading toward €8 bn a year.

The principle is the part worth importing. The network was adapted to what the customers had already installed, rather than the other way round.

253 V

Truth be told

One concession first, because it is a real one. Under the settlement rules as they stand, some of the cost of all this genuinely does land on neighbours who have no panels. That is a fair objection and it deserves a fair answer rather than a dismissal. Nobody should be asked to subsidise someone else's electricity.

What we truly need is a Romanian equivalent of the Dutch action plan: a published programme of network reinforcement with dates attached and consequences for distributors that miss them. That does not require anyone's inverter to be switched off from a control room.

Sources

ANRE figures via Economica.net and InvesTenergy; Greenpeace Romania on the AFM programme; Digi24 on the 2026 budget; Economedia and National.ro on disconnections and inverter settings; Energy Magazine on the 5 kW proposal; BBC News on the Iberian blackout; S&P Global and AOL/Reuters on the Dutch congestion plan; Puterea on Law 160/2026.

What follows is one house's answer to the same problem, arrived at from the other end. It does not wait for the network to be rebuilt and it does not ask a neighbour to pay for anything: it moves the largest load in the house to the hour the surplus exists, and holds the voltage at the feed-in point below the level at which the installation starts protecting itself.

Devices in the voltage path, and where each one acts
DeviceThreshold or ratingConsequence
Fronius Symo GEN24 Plus
inverter over-voltage response
253 VThe inverter curtails and stops. The threshold is a technician-level setting — the customer role cannot see or change it.
DigiTOP V-PRO-3F63A
three-phase voltage relay in the board
257 V
over-voltage cut-off
Physically disconnects the house to protect the equipment behind it. The only device in the installation that can open the circuit, and the only one of the three whose threshold is set on the device itself.
ETITEC V T2 surge arresters
on the incoming busbars, reference only
stamped 255/20Does not act at these levels: 255 V is Uc, the highest voltage it can sit at continuously, and 20 kA its nominal discharge current — neither is a trip point.

Before this build, the feed-in point regularly passed 253 V around 13:00. Every one of those excursions was production lost for good. The DigiTOP was installed together with the Wall Connector and was first set at 253 V, then at 255 V. The relay opened at least ten times a day and took the house with it. 257 V is the current setting, because DigiTOP is too sensitive.

The lever

Voltage at the feed-in point rises with exported power. Anything consumed on site never reaches the street, so local consumption at midday pulls the voltage back down. A battery would be the other way to absorb it and this is the next step, hopefully financed by Casa Verde 2026. The car is on the driveway through the working day, and needs to be charged anyway. Why not for free?

The design goal

Absorb the surplus exactly when the grid is congested. The car now takes the whole surplus and a little from the grid on top of it, and the feed-in point stays below 250 V instead of crossing 253 V. Nothing trips, and nothing is curtailed. This is not tuned for the cheapest kilowatt-hour — it is tuned for the highest local consumption during the hours the grid is weakest.

What the site has done so far

These are lifetime registers, read from the two Fronius meters.

Fronius meter registers
RegisterSourceValue
Lifetime PV yieldSolar meter — the inverter itself16,264.9 kWh
Lifetime exportGrid meter — energy (reverse)12,924.3 kWh
Lifetime importGrid meter — energy3,396.5 kWh
Export ÷ importderived3.81×

Subtracting export from yield puts self-consumption at roughly 3,340 kWh — about 20 % of everything the array has produced.

Section 03Architecture

Four devices, one local network, nothing external in the command path.

In short: the inverter measures, the Pi decides, the car obeys. The charger just delivers power and reports what it sees; the car is what actually adjusts how much it draws, one amp at a time, over Bluetooth. Everything runs inside the house — no cloud account is in the loop, and the internet can be down while the car charges.

Grid, inverter and PV array on the left; the Raspberry Pi controller in the centre; Wall Connector and Model 3 on the right. Power runs grid to inverter to array and grid to Wall Connector to car. Data runs inverter to Pi over Modbus and the Solar API, Pi to Wall Connector over HTTP, and Pi to car over Bluetooth LE. Grid three-phase, ~240 V Fronius Symo GEN24 + Smart Meter, feed-in point Modbus TCP 502 · Solar API 80 PV array 7 kWp CONTROLLER Raspberry Pi 4 evcc · TeslaBleHttpProxy InfluxDB · Grafana · logger Wall Connector Gen 3 three-phase, C32 circuit HTTP /api/1/vitals Tesla Model 3 start / stop amperage, 1–16 A MODBUS 502 SOLAR API HTTP BLUETOOTH LE power data Nothing in the command path leaves the local network.
Data and power. The Pi reads production and grid exchange from the inverter over Modbus, reads socket status from the Wall Connector over HTTP, and commands the car directly over Bluetooth LE.

Why the fine control happens on the car

A Gen 3 Wall Connector cannot be told what current to deliver. It reports — power, per-phase current and voltage, contactor state — but the interface is read-only; the only current it modulates by itself is the share it negotiates inside a power-sharing group. All of the modulation has to be commanded on the vehicle, which is why the Bluetooth bridge is the centrepiece of the build rather than a convenience.

Commanding the car also gets underneath the 6 A floor IEC 61851 puts on the pilot signal. At three phases and roughly 720 W per amp, 6 A means 4.3 kW of surplus before charging could begin at all; the car accepts 1 A, which drops the threshold to about 720 W. This belongs to vehicle control in general rather than to Bluetooth: the fleet API reaches the same currents.

Bluetooth or the fleet API

Both routes command the car. They differ in what they cost, what they depend on, and who else is in the path.

The two control paths
Bluetooth LE this buildFleet API
Runs onThe Pi, over BLE, 5–15 m from the carTesla's servers, over the internet
NeedsA paired key on the PiDeveloper account, app approval, OAuth client, access and refresh tokens
CostsNothing$10 monthly credit, then metered
Works when the car is awayNoYes
Third party in the pathNoneTesla, plus a command proxy
Why this build chose Bluetooth

The car is on the driveway, the Pi is a few metres away, and the one thing the fleet API offers — control from anywhere — is worth nothing here. Against that it wants an account, tokens, a public command proxy, an internet connection in the command path, and a meter that ticks every time the controller changes its mind. The controller changes its mind all day.

There is a second cost worth naming. A Tesla Wall Connector cannot control itself, so an internet-based setup needs a public command proxy to sign commands on the way through — another moving part, and a third party holding credentials to the car. The Bluetooth bridge is that proxy, running on the Pi, reachable by nobody outside the house.

Division of labour
ComponentResponsibility
Fronius Symo GEN24 PlusProduces. Also the solar meter — evcc reads the inverter's own SunSpec unit for PV power.
Fronius Smart MeterMeasures the grid exchange at the feed-in point. SunSpec unit 200, read through the inverter. This is the number Solar mode steers on.
Tesla Wall Connector Gen 3Switches the contactor and meters the charge. Serves as both the charger and the loadpoint's energy meter.
Raspberry Pi 4Runs everything: evcc, the BLE proxy, InfluxDB, Grafana and the custom scripts.
Tesla Model 3Accepts the commanded current. The only device in the chain that can go below 6 A.

Section 04The hardware

Three small purchases carry the whole control stack: a Raspberry Pi, a memory card for its system, and an ordinary USB disk for the measurement history. Everything else in this section is advice on where to put them.

The full list

  1. A Raspberry Pi 4 B, its original USB-C power supply, and a microSD card of 16 GB or more
  2. A USB disk for the history — any size, and an old spinning drive is fine.
  3. A laptop with Raspberry Pi Imager installed, and WinSCP or any SCP client.
  4. A Fronius GEN24 inverter on the same network, with a Smart Meter at the feed-in point read through it
  5. A Tesla Wall Connector Gen 3 and the QR-code card that shipped with it
  6. The car's NFC key card — the physical card, not the phone key
  7. The inverter's Customer password, or owner access to Fronius Solar.web to reset it
  8. A router with 2.4 GHz Wi-Fi and access to its admin page for DHCP reservations
  9. An Ethernet cable from the Pi to the router — the Pi runs wired
  10. A Wi-Fi range extender
Distance matters

The Pi has to sit within Bluetooth range of the car — realistically 5–15 m, through no more than one wall.

Why the history does not live on the card

A microSD card is a device with a finite number of writes. Good for OS, but not for a database.

So InfluxDB's two volumes go on a USB disk. An old 2.5” or 3.5” drive you already own is the right answer here — the database is a few gigabytes a year and it is written in a steady trickle, so neither capacity nor speed is the constraint.

Everything else stays on the card: Docker images, Grafana's own volume, evcc's database.

Put it in a black port, not a blue one. SuperSpeed USB radiates broadband noise straight across 2.4 GHz — Intel documented it in 2012 and every USB 3 enclosure since has done it to some degree. On most machines that is just a curiosity. The USB 2 ports give 480 Mbit/s, which is hundreds of times more than needed, so the trade costs you nothing at all. Since Pi is close to the router, because it is wired, so the Bluetooth can own the Pi's antenna, avoiding 2.4 GHz noise becomes a necessity.

Measured, on the disk this build uses. A 500 GB USB drive in a Gembird 2.5” USB 3.0 enclosure — the kind that has been in a drawer for a decade. At the rate the two loggers write, capacity is not the number that will ever run out on this disk.

Section 05The inverter

Two interfaces to switch on: Modbus for evcc, the Solar API for the voltage readings that feed the dashboard. Both are a web form in the inverter, reached from a browser on the home network, so this is done before the Pi is built rather than after — and the installer, when it runs, finds them already answering.

The inverter has two switches to flip in its own web page — one lets the Pi read the meters, the other exposes the voltages.

Fronius Symo GEN24 Plus, wall-mounted outdoors with the DC rotary disconnector at the left. Power LED green, connectivity LED blue. Modbus TCP on 502, Solar API on 80.

If you do not have the Customer password

No installer is needed. The registered system owner in Solar.web can reset it:

  1. Tap the optical sensor on the front of the inverter once — the right-hand LED flashes blue and the WLAN access point opens
  2. Open the inverter's web interface in a browser, from the home network, at its usual address
  3. Click Forgot password? for the Customer role — a PIN is generated
  4. Go to Fronius Solar.web → Settings → Components → Actions on the inverter → Reset Customer Password
  5. Enter the PIN, take the recovery key, set the new password
Owner only

The function only appears to the account registered as owner of the system in Solar.web. The Technician password — the role that holds the 253 V over-voltage setting — can be reset solely through the installer or Fronius support.

Modbus TCP — the data source for evcc

In the inverter's interface: Communication → Modbus.

Modbus TCP settings
SettingValue
Slave as Modbus TCPenabled
Port502
SunSpec model typeint + SF
Meter address200

Save. The inverter may briefly report a stopped state — it is restarting its communication side and comes back on its own within a minute or two.

Two SunSpec units on one address

The inverter is always unit 1; the Smart Meter is unit 200. Both are reached at the same IP and port. That distinction is what lets one device serve as both the solar meter and the grid meter — and getting it right is the difference between a measured PV figure and a derived one.

Solar API — the voltage source for the logger

Communication → Solar API → enabled. It coexists with Modbus without issue.

Section 06The Wall Connector

All it has to do is join the network; everything else is read locally.

The charger only needs to join the IoT 2.4GHz Wi-Fi, from the Tesla app, standing next to it. After that it is never configured again — it just reports what it sees.

From the Tesla app: profile → Add product → Wall Connector, then scan the QR code from the quickstart card or the label on the side of the unit. The app connects over Bluetooth, so stand near the charger, then join it to the Wi-Fi. Once connected, find it in the router's client list and reserve whatever address it picked up. The installer never asks for it and never records it — the Wall Connector belongs to evcc, and you enter this address once in evcc's browser configuration. Reserve it first so that what you type there stays true.

Browser
http://CHARGER_IP/api/1/vitals
Pass · JSON beginning "contactor_closed":false,"vehicle_connected":false — exactly right with nothing plugged in.

The whole response is worth reading once, because it is the only view of the charger you get without a car attached, and three of its numbers are easy to misread later.

Responsenothing plugged in
{"contactor_closed":false,"vehicle_connected":false,"session_s":0,
 "grid_v":225.2,"grid_hz":49.936,"vehicle_current_a":0.3,
 "currentA_a":0.3,"currentB_a":0.3,"currentC_a":0.3,"currentN_a":0.5,
 "voltageA_v":0.0,"voltageB_v":2.8,"voltageC_v":3.0,"relay_coil_v":12.0,
 "pcba_temp_c":37.0,"handle_temp_c":44.4,"mcu_temp_c":46.8,
 "uptime_s":587301,"input_thermopile_uv":-329,"prox_v":0.0,
 "pilot_high_v":12.0,"pilot_low_v":12.0,"session_energy_wh":702.500,
 "config_status":5,"evse_state":1,"current_alerts":[]}

Section 07The distribution board

An outdoor enclosure on the exterior wall, directly above the Wall Connector. Four rows, with the instruments in the bottom compartment.

The electrician's cabinet, photographed here so you know what is what.

Board and charger, as mounted. The enclosure on the exterior wall with the Gen 3 Wall Connector directly beneath it, cable coiled. The short run between the two is the AUTO circuit.
The board, open. Top row: surge protection and the incoming grid busbars (REȚEA). Second: house (CASĂ) and inverter (INVERTOR). Third: car (AUTO), busbars and main (GENERAL). Bottom: the Fronius Smart Meter that evcc reads as SunSpec unit 200, beside the DigiTOP V-PRO-3F63A displaying L1, L2 and L3.
Board inventory
LabelMeaningDevice
REȚEAGrid, incomingBusbars L1/L2/L3/N; ETITEC V T2 surge arresters, stamped 255/20
CASĂHouseNoark, 4-pole; three single-pole Legrand breakers in the top row
INVERTORInverterETI, 4-pole
AUTOCar / Wall ConnectorNoark Ex9B C32, 415 V~
GENERALMainNoark Ex9B C40, 415 V~
InstrumentsFronius Smart Meter (SunSpec unit 200) · DigiTOP V-PRO-3F63A True RMS

Section 08Network and addressing

Everything lives on one home network. Three devices get fixed addresses on the router, the Pi runs on a cable, and the two outdoor devices stay on 2.4 GHz Wi-Fi.

Addresses and ports
ElementAddressPortRole
Fronius Symo GEN24INVERTER_IP502 · 80Modbus TCP (SunSpec) and Solar API
Fronius Smart Metervia the inverterSunSpec unit 200Grid meter at the feed-in point
Tesla Wall ConnectorCHARGER_IP80/api/1/vitals
Raspberry PiPI_IPHost for everything below
evcc UI & APIPI_IP7070Control, configuration and statistics
Tesla BLE proxyPI_IP8080Key generation, pairing, commands to the car
InfluxDBPI_IP8086Time-series store, org home, buckets evcc and system
GrafanaPI_IP3000Dashboards
Router adminROUTER_IP8443Network settings

The Pi runs wired, with its Wi-Fi radio off

On a Raspberry Pi 4, Wi-Fi and Bluetooth share one antenna. Network traffic on the Wi-Fi radio destabilises the Bluetooth link to the car, which is the one link in this system that carries commands rather than readings. So the Pi never joins Wi-Fi at all, it is wired before it is first powered on, it takes its address from a DHCP reservation, and its Wi-Fi radio is switched off for good by the installer. One wired path, one radio doing one job. The installer does this itself, and refuses to do it while eth0 is still down.

The Wall Connector and the inverter stay on Wi-Fi, on the router's IoT SSID — both refuse 5 GHz networks, so it has to be the 2.4 GHz band.

The SSID is separate. The subnet is not.

Putting the two outdoor devices on an IoT network is worth doing for tidiness, but it is not isolation. Everything still shares one flat subnet, and it has to: the Pi is wired, the inverter and the Wall Connector are wireless, and every reading in this build crosses between them — Modbus TCP and the Solar API to the inverter, HTTP to the charger. A separate SSID with no VLAN behind it is a naming convention rather than a boundary — worth knowing rather than assuming otherwise.

Section 09Pi OS install

Put the operating system on the card with Raspberry Pi's own tool, plug in network and power, and log in once from the laptop.

Imager settings
SettingValue
DeviceRaspberry Pi 4
Operating systemRaspberry Pi OS Lite (64-bit), under “Raspberry Pi OS (other)”
StorageThe SD card — then enter the customisation wizard
Hostnameevcc
LocationYour city. It sets the timezone every Grafana timestamp depends on — every point in InfluxDB is UTC, and this is what turns it back into the hour you actually charged
User accountUsername and password, written down — the installer asks for that username
Wi-FiLeave it blank. The Pi is wired, and the installer blocks this radio anyway — filling it in here only creates a credential to be revoked later and an antenna the Bluetooth link has to share
SSHEnabled, with password authentication — enough on a trusted LAN; keys are the stricter option
Raspberry Pi ConnectNot needed — everything happens on the local network

Start from a genuinely blank card. A card carrying an old partition table is the most common reason a fresh image refuses to boot. Open a Command Prompt as administrator on the laptop — this step assumes Windows.

Read the disk number twice

clean is not reversible and it does not ask. Point it at the wrong number and it wipes that drive — the laptop's own disk is in the same list. Disk numbers change between machines and between reboots, so identify the card by its size, every time, and never by a number remembered from last time.

Command Prompt, as administratoron the laptop
diskpart

list disk          // identify the card by its size, not its number
select disk 1      // substitute the number you just read
clean
exit
Pass · DiskPart succeeded in cleaning the disk. Windows will then offer to format the card — dismiss it. Raspberry Pi Imager writes its own filesystem next.
DiskPart session: list disk shows three disks, select disk 2 chooses the card by its size, then clean and the line DiskPart succeeded in cleaning the disk
The clean, as it actually ran. Three disks in the list, and the card is found by its size — here it was disk 2, not the 1 in the recipe above, which is exactly why the number is read from the list and never typed from memory. Then clean, one line of confirmation, and the card is bare metal for the Imager.

Then Raspberry Pi Imager, with the customisation wizard:

Imager device list with Raspberry Pi 4 selected
Device. Raspberry Pi 4.
Operating system list scrolled to Raspberry Pi OS (other)
Operating system, first list. Raspberry Pi OS Lite hides under “Raspberry Pi OS (other)”.
Raspberry Pi OS Lite 64-bit entry, cached on this computer
The image. Raspberry Pi OS Lite (64-bit) — Debian Trixie with no desktop, and the 64-bit build the container stacks need. “Cached on your computer”: this card was not its first.
Storage list showing the 28.8 GB card reader device
Storage. The 28.8 GB card in its reader, alone in the list — “Exclude system drives” keeps it that way.
Hostname field set to evcc
Hostname. evcc — the name the router's client list will show.
Localisation set to Bucharest, Europe/Bucharest, keyboard us
Localisation. Bucharest sets Europe/Bucharest — the timezone every Grafana timestamp depends on. Keyboard stays us.
Username pi with saved password fields
User account. Username and password, written down — the installer asks for that username.
Wi-Fi form left empty
Wi-Fi. Left blank, deliberately. The Pi is wired, and the installer blocks this radio anyway.
SSH enabled with public key authentication, one key configured
SSH. Enabled. This build went the stricter way — public-key authentication, one key configured.
Raspberry Pi Connect toggle off
Raspberry Pi Connect. Off — everything happens on the local network.
Write image summary listing device, OS, storage and four customisations
The summary. Four customisations to apply, and the write is armed.
Erase warning dialog counting down
The erase dialog counts down… — the confirm button stays dead for a few seconds, so it cannot be clicked on reflex.
Erase dialog armed with cancel and confirm buttons
…then arms. “I UNDERSTAND, ERASE AND WRITE” — the last moment a card's old life can be saved. This one has none; diskpart already made it bare metal.
Writing in progress at 2 percent
Writing. Do not disconnect the storage device — it means it.
Write complete screen with four green ticks
Write complete. Four ticks, the card ejected automatically, ready for the Pi.

Section 10The software install

Everything the Pi runs arrives as one release from the public repository, and one script installs it. Two lines at the terminal, one run, and — on a fresh machine — one reboot.

Log in from the laptop — ssh USERNAME@PI_IP, the account created in the Imager wizard, at the address the router reserves for the Pi — and run:

Terminal, over sshon the Pi
curl -fsSLO https://github.com/mihai-gafiteanu/solar-surplus-releases/releases/latest/download/get.py
sudo python3 get.py

The script proves the release before the system is touched: it resolves the newest tag, downloads the package and its sha256, verifies the hash, checks that the package's stated version agrees with the tag, unpacks it into a scratch directory and runs the release's own selftest inside it — a release that fails its own checks is refused whole. Only then is the package installed, at /usr/lib/solar-surplus, and the installer run in the same foreground to build the machine: system packages, Docker, evcc, the containers, the tokens, the board, both loggers, the nightly maintenance.

The installer asks eight questions, once — the account name, the Pi's and the inverter's addresses, the disk's mount point, and the four that name the network share the nightly backup pushes to, the password typed hidden. The answers are saved to /etc/solar-surplus/install.conf, so a second run asks nothing. Six secrets are generated and none is printed to the screen: they land in one KeePass import file in the home directory, mode 600. Copy it to the machine KeePass runs on, import it, then delete both copies.

The history disk is required, and the installer sorts it out

InfluxDB keeps every reading for ever — a write load a microSD card is not built for — so the history lives on a USB disk. A disk already labelled influxdb, from a previous life of this system, is adopted with not one byte written to it. A blank USB disk is offered, and preparing it is the one destructive act in this build, so it happens only after you type the device path in full. No disk, and the run stops rather than fall back to the card: the card would work, and it would wear out.

A fresh install ends by announcing its reboot a minute ahead — sudo shutdown -c cancels it — and everything just built comes back on its own, which is the proof. The installer can be run again at any time: every step checks its own work first, and it never overwrites a file you changed.

Three things it cannot do for you
WhatWhere it happens
Switch on Modbus TCP and the Solar APIThe inverter's web interface
Join the Wall Connector to Wi-FiThe Tesla app, next to the charger
Generate the BLE key and pair the carYou, in the car, with the key card

It checks the first and the third and prints whichever is still outstanding. Taken in order, the inverter is already switched on by now, so a first run leaves only the car.

Section 11evcc configuration

The installer builds the stack; evcc still has to be told what hardware it talks to. That happens once, in the browser, at http://PI_IP:7070. Fifteen screens, in the order they appear.

The first visit asks for a password, then offers a wizard.

Set administrator password dialog with new and repeat password fields
The administrator password. Set on the first visit. It locks the settings; the main screen stays readable without it.
Welcome screen saying hello aboard, with a start configuration button
The wizard. One button starts the configuration.
Sponsorship dialog: you are a supporter, sponsor token saved, confetti across the screen
The sponsor token. Support for most chargers, the Wall Connector included, needs one. It is pasted once, under General.
The configuration page, still empty: general settings on top, then blank tiles for charging points, vehicles, consumers, grid and solar
The configuration page. Empty at the start — a tile each for the charger, the vehicle, consumers, grid and solar. The rest of this section fills them in.

The charger first.

Chooser dialog: add charging point or add heater
Charging point or heater. A charging point.
Add charging point dialog with the title Tesla Wall Connector (Gen 3)
The name. Any title works; this one says what the hardware is.
Manufacturer list scrolled to Tesla Wall Connector (Gen 3)
The manufacturer. Tesla Wall Connector (Gen 3), picked from the list.
Add charger dialog: a note that control goes through the vehicle, IP address filled in, validation successful with live readings
Address and validation. The charger at its reserved address, answering with live numbers. The note above repeats the design: the box takes no commands — the car does.
Charging point created dialog with a link to advanced configuration
Created. Sensible defaults. Advanced configuration opens the rest.
Edit charging point: default mode solar, custom thresholds, current 1 to 16 A, three phases
The charging point, opened again. Default mode Solar, with custom thresholds: start after one minute of enough surplus, stop after ten minutes without. Current from 1 to 16 A, on three phases.

Then the car. The Pi runs a Bluetooth proxy with its own page, at http://PI_IP:8080. The key is generated there and sent to the car once.

TeslaBleHttpProxy page: key management with no keys yet, key roles explained, and the setup vehicle form
The proxy’s page, before any key. Two roles on offer. Charging Manager is the limited one — it can read the car and manage charging, nothing more. That is the one this build uses.
The proxy page after generating: charging manager key active, setup vehicle form ready to send the key
The key, active. Generated in one click. Below, the same page sends it to the car: car awake, VIN typed, Send Key to Vehicle — then the key card on the console accepts it.
Edit vehicle dialog: Tesla BLE, title Tesla Model 3 2026, proxy URL and port 8080, battery capacity 60 kWh
The vehicle in evcc. Manufacturer Tesla BLE, pointed at the proxy — the Pi’s own address, port 8080. The VIN again, and the battery: 60 kWh.

Last, the two meters. Both answer at the inverter’s address; the unit ID tells them apart.

Edit grid meter: Fronius Symo GEN24 Plus, meter ID 200, validation successful with energy both ways and three phases
The grid meter. The Fronius Smart Meter: the inverter’s address, port 502, unit ID 200. Validation reads it live — energy both ways, and all three phases.
Edit solar meter: Fronius Symo GEN24 Plus at the same address, maximum AC power 6000 W, validation successful, curtailable yes
The solar meter. The inverter itself, same address and port. Maximum AC power set to 6,000 W; validation reports it curtailable.

Everything entered here lands in one file, evcc.db, and the nightly backup carries it.

Section 12The nightly backup

The Pi backs itself up at half past ten every evening, before it updates anything. Five archives, each one proved before it is trusted.

The five archives
NameWhat it carriesWhy it matters
answers/etc/solar-surplus — the eight answers and every generated secretA few kilobytes that are the install's identity. Restored first, they turn a reinstall into a continuation
keyThe car-pairing keyThe one thing that cannot be regenerated — a new key means the key-card ceremony in the car, again
evcc-dbevcc.db — the configured devicesThe loadpoint, the charger, the car
influxdbThe history, and every file carrying a token it honoursEvery reading since the build went live
grafanaThe Grafana data volumeThe board itself ships with every release; the volume carries what was changed on it

Each archive is named <name>-backup-YYYY-MM-DD.tar.gz and written twice over. First to the history disk itself, under /mnt/influxdb/backups — a tier that survives a card failure and rides the disk into the next install. Then to the Samba share named at install time, pushed over smbclient — deliberately not a mount, so a sleeping NAS can never hang a boot — and every push is proved: the copy is fetched back off the share and its sha256 compared before the run believes it. Rotation keeps the newest fourteen of each small archive and the newest four of the history, on both tiers alike.

Nothing needs to be run — the evening window does this on its own. A backup on demand is one command:

A backup, right nowon the Pi
sudo systemctl start maintenance-backup.service

Section 13Restore

The dead card is the rehearsed case, not the disaster. A reinstall is the same install run over the archives — one run, one stop, one reboot.

  1. While the old Pi still answers — run one last backup, and copy the answers and key archives to the laptop as well: until the new card boots, the history disk is the identity's only carrier.
  2. Fresh card — image it exactly as before, boot, and run the same two install lines. The run brings the OS current first; that is the long wait.
  3. The stop. A machine with no identity halts before the first question and lists the newest archives it found — read straight off the history disk, plus anything copied to /tmp. One Enter takes the identity back: the answers land, and nothing is asked.
  4. The rest is the run's own work. The key, the devices and the board go back once the stacks they unpack into exist. A history archive is refused — the surviving disk is newer than any copy of it, and the refusal is the correct outcome. The run ends in the announced reboot.
  5. When it comes back — the board's history reaches back before the reinstall, and the car accepts the restored key with no ceremony, no card, no driver's seat: restoring the key is the pairing. Proved on a real reinstall, 15 August 2026.
A surviving history disk is never overwritten

Its data is newer than any archive, so the restore refuses the history and says so. If an install ever offers to erase the history disk instead, stop it — that means the answers did not come back, and the run is minting a new identity rather than continuing yours.

The same script restores by hand, for surgery on a live machine — one archive back, or all of them. When the disk died too, --fetch first pulls the newest set back off the share:

The hand verbson the Pi
sudo python3 /usr/lib/solar-surplus/files/maintenance-backup.py --fetch /tmp
sudo python3 /usr/lib/solar-surplus/files/maintenance-backup.py --restore key

Section 14Maintenance and updates

One maintenance window a day, at 22:30 — past the year's latest sunset, so it never competes with surplus charging. Backup, then updates, then a reboot that rehearses the cold start. The whole story is told before you sleep.

  1. Backup. The five archives of the previous sections, pushed and proved.
  2. Updates. Six components in order: the stack itself first, so the new release's rules govern the evening — then evcc, Grafana, InfluxDB, the BLE proxy, and Raspberry Pi OS last, the one that can ask for a reboot.
  3. Reboot. Guarded — never under a running window, never mid-install — and deferred with a note when the guard says no. The daily reboot is a feature: the cold-boot path is rehearsed every day, so the day it matters is never its first.
  4. On boot, the report. The Pi waits for its four services to answer, writes down what came up and how many seconds it took, then takes the day's first version reading.

The policy: everything up to a major version is taken by the window itself. Only a major waits — for one command from the owner, which raises the pin and updates in the same run — and the raise lands in a file no release ever rewrites, so the word, once given, survives every later install. Between windows, a versions table repaints itself every quarter of an hour: what is installed, what is published, which of the two is ahead. Nothing here waits for the clock, either: the whole window, or any single component, starts on demand with one systemctl start.

A bad release is not repaired in place — it is retreated from. Any published release installs by tag, over the same proving ladder as the newest, and the last three verified packages are cached on the Pi itself, for the day GitHub is out of reach:

Rollbackon the Pi
sudo python3 get.py --tag vX.Y

And when something feels wrong before anything is known, the release checks itself — no unit started, no network touched, nothing written:

The selfteston the Pi
python3 /usr/lib/solar-surplus/selftest.py
Pass · 482 passed, 0 failed — the count grows with the releases; the zero is the number that matters.

Section 15The board

Everything the build measures lands on one screen. It answers three questions: is the car charging, is the Pi healthy, and did last night’s chores run. Here is the whole board, piece by piece, as it looked live on 24 August 2026.

The board comes with the install and lives at http://PI_IP:3000. The left half is the Pi. The right rail is history. The bottom half is the car and the sun.

Gauges for temperature, CPU, load, memory and disks; uptime and four green power cells
Vitals. Heat, processor, memory, disks. The temperature arc goes orange over 60 °C and red over 80. The four cells at the bottom stay green unless power or heat was ever a problem since the last boot.
Two maintenance buttons, the night's log, car charge, BLE proxy, update status and container list
Maintenance. Do the needful runs the nightly routine — backup, updates, guarded reboot — right now instead of at 22:30. The log under it shows what tonight's window did. On the right: the car's charge, the BLE bridge that commands the car, and whether any software waits on a human. Green means no.
Availability bars for the four services and their response time
The rail, top. Are the four services up, and how fast they answer. Ten more strips below it — network, heat, load, memory, swap, disk — all read the same way: flat is good.
Full-width yellow banner reading Charging - mixed
The banner. Readable from across the room: is the car charging, and on what. Green is sun alone, yellow is sun topped up with grid, grey is idle.
Live power flow diagram between sun, house, grid and car
Live flow. At capture time: 3.9 kW from the roof — 1.6 kW running the house, about 1 kW trickling into the car, the rest flowing out to the grid. The dots move on the real board.