How a Fronius GEN24 inverter, a Tesla Wall Connector Gen 3 and a Model 3 were tied together with a Raspberry Pi 4, so the car charges on its own surplus — and pulls hardest exactly when the grid is drowning in sunshine.
Everything that follows serves this diagram. The car charges from surplus whenever surplus exists; when voltage at the feed-in point climbs — the sign that the whole neighbourhood is exporting and the inverter is about to derate — charging switches to a guaranteed mode and soaks up everything it can.
Four devices, one local network, nothing external in the command path.
The Pi reads production and grid exchange from the inverter, reads socket status from the Wall Connector, and commands the car directly over Bluetooth. A Gen 3 Wall Connector cannot adjust its own current, so the fine control happens on the car — which is why the Bluetooth bridge is the centrepiece rather than a nicety.
The full list, before touching anything.
Raspberry Pi Imager 2.x, with the step-by-step wizard.
evccWindows already ships an SSH client; nothing to install.
Open PowerShell and connect. The part before the @ is the username, which is why it is never asked for separately. Nothing appears on screen while typing the password — that's normal.
ssh pi@evcc.localIf the name doesn't resolve, find the IP in the router's client list (the host shows up as evcc) and use ssh pi@192.168.50.x. Once in, update the system:
sudo apt update && sudo apt full-upgrade -y
sudo rebootThe brain of the system, from an official apt repository.
curl -1sLf 'https://dl.evcc.io/public/evcc/stable/setup.deb.sh' | sudo -E bash
sudo apt update
sudo apt install -y evcc
sudo systemctl enable --now evccCheck the service, then open the interface at http://evcc.local:7070. It will report configuration errors until the following steps are done — that's expected.
systemctl status evccTwo interfaces to switch on: Modbus for evcc, Solar API for voltage readings.
No installer needed. The registered system owner in Solar.web can reset it:
In the inverter's interface: Communication → Modbus
| Setting | Value |
|---|---|
| Slave as Modbus TCP | enabled |
| Port | 502 |
| SunSpec model type | int + SF |
| Meter address | 200 |
Save. The inverter may briefly report a stopped state — it restarts its communication side and comes back on its own within a minute or two. Verify from the Pi that the port answers:
nc -zv 192.168.50.99 502Connection refused means Modbus isn't on, or the address belongs to another device. succeeded means you can move on.
Communication → Solar API → enabled. It coexists with Modbus without issue. Test:
curl -s "http://192.168.50.99/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0" \
| jq '.Body.Data.Voltage_AC_Phase_1'A number around 230 means it works. null means Solar API isn't enabled.
All it has to do is join the home network; the rest is read locally.
Easiest route, from the Tesla app: profile → Add product → Wall Connector → 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 Wi-Fi.
Fallback, if the app doesn't offer it: switch the charger's breaker off and back on, connect a phone to the TeslaWallConnector_XXXXXX network (password on the same card) and open http://192.168.92.1. The access point is only live for about 15 minutes after power-up.
Once connected, note the IP from the router's list, give it a DHCP reservation, and check the local interface in a browser:
http://WALL_CONNECTOR_IP/api/1/vitalsThe response is JSON starting with "contactor_closed":false,"vehicle_connected":false — exactly right when nothing is plugged in.
Docker, TeslaBleHttpProxy, and a key with limited rights.
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USERLog out with exit and reconnect so the group membership takes effect.
mkdir -p ~/TeslaBleHttpProxy/key
nano ~/TeslaBleHttpProxy/docker-compose.ymlservices:
tesla-ble-http-proxy:
image: wimaha/tesla-ble-http-proxy
container_name: tesla-ble-http-proxy
volumes:
- ~/TeslaBleHttpProxy/key:/key
- /var/run/dbus:/var/run/dbus
restart: always
privileged: true
network_mode: host
cap_add:
- NET_ADMIN
- SYS_ADMINcd ~/TeslaBleHttpProxy
docker compose up -dOpen http://evcc.local:8080/dashboard and click Generate for the Charging Manager role. That role can read data and wake the car, start and stop charging, and set the amperage — it cannot unlock or drive. If the Pi were ever stolen, the key opens nothing.
Pairing, in exactly this order:
Final test of the chain, with the real VIN in the URL:
http://evcc.local:8080/api/1/vehicles/REAL_VIN/vehicle_data?endpoints=charge_state&wakeup=trueJSON with the battery state means the bridge both reads and commands the car. The first request takes a few seconds while the Bluetooth connection is established.
The one part of the chain that can vanish silently after a power cut.
Linux can block the radio through rfkill. The symptom is unmistakable: hci0: can't up device: operation not possible due to RF-kill. Immediate fix:
sudo rfkill unblock bluetooth
sudo systemctl restart bluetooth
rfkill listTo guarantee the state at every boot, a tiny service that runs once and exits:
[Unit]
Description=Unblock Bluetooth at boot
Before=bluetooth.service
[Service]
Type=oneshot
ExecStart=/usr/sbin/rfkill unblock bluetooth
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable bt-unblockcontext deadline exceeded errors). If those show up often, move the Pi onto Ethernet and run sudo rfkill block wifi — the radio then belongs to the car alone.One file ties the inverter, the charger and the car together.
network:
schema: http
host: evcc.local
port: 7070
interval: 30s
meters:
- name: grid
type: template
template: fronius-gen24
usage: grid
host: 192.168.50.99
- name: pv
type: template
template: fronius-gen24
usage: pv
host: 192.168.50.99
chargers:
- name: wallconnector
type: template
template: twc3
host: WALL_CONNECTOR_IP
vehicles:
- name: model3
type: template
template: tesla-ble
title: Model 3
vin: REAL_VIN
url: localhost
port: 8080
capacity: 60
site:
title: Home
meters:
grid: grid
pv: pv
loadpoints:
- title: Garage
charger: wallconnector
vehicle: model3
mode: pv
phases: 3
mincurrent: 2
maxcurrent: 16
enable:
delay: 1m
threshold: 0
disable:
delay: 15m # ride through brief cloud gaps so the car sleeps less
threshold: 0Two values get replaced with your own: the Wall Connector's IP and the VIN. Indentation stays exactly as shown — YAML counts spaces.
evcc --config /etc/evcc.yaml --ignore-db checkconfig
sudo systemctl restart evcc--ignore-dbThe database belongs to the system service. Without this flag the command stops with a message about the database instead of checking the configuration.Why mincurrent: 2: on three-phase, the standard 6 A minimum means a start threshold of roughly 4.1 kW of surplus — rarely reached on a 7 kW array once the house has taken its share. Over Bluetooth the car can go far below that, which moves the threshold to about 1.4 kW and makes mediocre days useful too.
disable.delay (and interval: 30s)In PV mode a passing cloud can drop the surplus for a minute or two. Without a delay evcc pauses, the Tesla falls asleep, and waking it again over Bluetooth to resume is the least reliable step in the whole chain — it shows up as charger out of sync (step 13). A 15-minute disable delay lets charging ride through those dips so the car stays awake instead of napping and failing to restart. interval: 30s is set for the same family of reasons: Bluetooth commands are slow, and a tighter loop cancels them mid-flight (context canceled).Five minutes now, instead of a month of odd charging sessions.
systemctl status evcc --no-pager -l | head -5
docker ps
rfkill list
sudo journalctl -u evcc -n 50 --no-pager| Check | Correct result |
|---|---|
| PV production in the interface | matches what Solar.web shows |
| Meter sign — switch on a big load (kettle, oven) | grid import increases. If it drops, the sign is inverted and the system would charge at exactly the wrong times |
| The "Garage" loadpoint | visible, no vehicle connected |
| The Bluetooth bridge container | Up |
Bluetooth in rfkill list | Soft blocked: no |
After sudo reboot | all of the above come back unaided |
The reboot test is the most important of them all: it proves the system survives a power cut without intervention.
Set the daily charge limit in the car (say 80%) — evcc decides when and how fast, the car decides how far. Plug in, and in PV mode the system waits for the surplus it needs and starts on its own. For immediate confirmation regardless of the weather, switch the loadpoint to Fast temporarily: charging should start within about 30 seconds, which validates the entire command chain in one move. Then back to PV.
The part no off-the-shelf system gives you.
On a very sunny day the whole street is exporting and the voltage at the feed-in point rises. Above roughly 253 V the inverter is obliged to curtail production — energy lost for good. Consuming locally in exactly that window keeps voltage in check and rescues production that would otherwise be cut.
evcc has no voltage trigger, so we add one: a service that reads phase voltages every 30 seconds and changes the loadpoint mode when thresholds are crossed.
There are really two rules layered together. A daily window — 09:00 to 19:15 — decides whether to charge at all: outside it the loadpoint is forced off, so evcc stops commanding the car and lets it sleep. Inside it, the voltage tiers decide how: plain PV on surplus, and Min+PV (boost) while the feed-in voltage is high.
sudo apt install -y jq bc
sudo nano /usr/local/bin/voltage-watch.sh#!/bin/bash
# Two-tier solar charging with a daily charging window, edge-triggered.
#
# Charging window: 09:00-19:15 (Pi local time). Outside it the loadpoint is
# forced OFF, so evcc stops commanding the car and lets it sleep instead of
# fighting the car's own schedule.
# Inside the window:
# normal -> PV mode (surplus charging, low threshold)
# V >= V_ON -> Min+PV (uninterrupted charging while voltage is high)
# Between transitions it commands nothing, so manual choices stick.
INVERTER="192.168.50.99"
EVCC="http://localhost:7070"
LP=1
V_ON=250.0
V_OFF=235.0
ON_DELAY=2 # 2 x 30s = 1 minute above the threshold
OFF_DELAY=10 # 10 x 30s = 5 minutes below it
INTERVAL=30
DAY_START=900 # 09:00 as HHMM, Pi local time - window opens
DAY_END=1915 # 19:15 as HHMM, Pi local time - window closes
above=0; below=0
zone="normal" # voltage belief: normal|boost
period="" # window belief: day|night ("" = first pass)
# current loadpoint mode from evcc (empty string on error)
lp_mode() { curl -s --max-time 5 "$EVCC/api/state" | jq -r ".loadpoints[$((LP-1))].mode // empty"; }
# set loadpoint mode
set_mode() { curl -s -X POST "$EVCC/api/loadpoints/$LP/mode/$1" >/dev/null; }
while true; do
# which side of the daily window are we on?
now=$((10#$(date +%H%M)))
if [ "$now" -ge "$DAY_START" ] && [ "$now" -lt "$DAY_END" ]; then cur="day"; else cur="night"; fi
# one authoritative read of the mode per cycle; skip the cycle if evcc is down,
# so a transient outage can't make us miss a window edge
mode=$(lp_mode)
if [ -z "$mode" ]; then
echo "$(date '+%F %T') evcc unreachable, retrying"
sleep "$INTERVAL"; continue
fi
# --- daily window (edge-triggered) ---
if [ "$cur" != "$period" ]; then
if [ "$cur" = "night" ]; then
# outside 09:00-19:15: stop charging, let the car sleep
if [ "$mode" != "off" ]; then
set_mode off; mode="off"
echo "$(date '+%F %T') outside window (09:00-19:15) -> OFF"
fi
above=0; below=0; zone="normal" # reset the boost latch for tomorrow
else
# window opens in the morning: resume surplus charging
if [ "$mode" = "off" ]; then
set_mode pv; mode="pv"
echo "$(date '+%F %T') window open (09:00-19:15) -> PV"
fi
fi
period="$cur"
fi
# at night, don't poll voltage or touch modes again
if [ "$cur" = "night" ]; then
sleep "$INTERVAL"; continue
fi
# --- voltage tiers (only inside the window) ---
json=$(curl -s --max-time 10 "http://$INVERTER/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0")
v1=$(echo "$json" | jq -r '.Body.Data.Voltage_AC_Phase_1 // 0')
v2=$(echo "$json" | jq -r '.Body.Data.Voltage_AC_Phase_2 // 0')
v3=$(echo "$json" | jq -r '.Body.Data.Voltage_AC_Phase_3 // 0')
vmax=$(echo "$v1 $v2 $v3" | tr ' ' '\n' | sort -g | tail -1)
if [ "$vmax" = "0" ]; then
echo "$(date '+%F %T') meter read failed, skipping"
sleep "$INTERVAL"; continue
fi
if (( $(echo "$vmax >= $V_ON" | bc -l) )); then
above=$((above+1)); below=0
elif (( $(echo "$vmax < $V_OFF" | bc -l) )); then
below=$((below+1)); above=0
else
above=0; below=0
fi
# rising edge: normal -> boost
if [ "$zone" = "normal" ] && [ $above -ge $ON_DELAY ]; then
zone="boost"
if [ "$mode" = "pv" ]; then
set_mode minpv
echo "$(date '+%F %T') Vmax=${vmax}V -> BOOST (Min+PV)"
else
echo "$(date '+%F %T') Vmax=${vmax}V -> boost zone, but mode=$mode (manual), leaving alone"
fi
fi
# falling edge: boost -> normal
if [ "$zone" = "boost" ] && [ $below -ge $OFF_DELAY ]; then
zone="normal"
if [ "$mode" = "minpv" ]; then
set_mode pv
echo "$(date '+%F %T') Vmax=${vmax}V -> normal (PV)"
else
echo "$(date '+%F %T') Vmax=${vmax}V -> normal zone, but mode=$mode (manual), leaving alone"
fi
fi
sleep "$INTERVAL"
donesudo chmod +x /usr/local/bin/voltage-watch.sh[Unit]
Description=Voltage-triggered EV charging watchdog
After=network-online.target evcc.service
[Service]
ExecStart=/usr/local/bin/voltage-watch.sh
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now voltage-watch
journalctl -u voltage-watch -fsudo timedatectl set-timezone Europe/Bucharest. A fresh Raspberry Pi OS defaults to UTC, which would shift the cutoff by hours; verify with timedatectl. Daylight saving then follows on its own.| Moment | What happens |
|---|---|
| 09:00 — window opens | PV mode: charges at 2–6 A whenever surplus passes ~1.4 kW, pauses under cloud |
| Midday, voltage above 250 V for a minute | Switches to Min+PV: charging no longer pauses and absorbs the full surplus |
| Afternoon, below 235 V for five minutes | Returns to normal PV mode |
| 19:15 — window closes | Loadpoint forced off; the car stops being asked and can sleep |
| Any time you pick a mode manually | The script stands down and notes it in the log |
OFF_DELAY or widen the gap between thresholdsV_ON (250 V is conservative; 245 V catches more)V_OFF (235 V) sits close to normal grid voltage, so the “five minutes below” rarely completes in the evening — lower it toward 230 V. The 19:15 window closes it out regardless.DAY_START/DAY_END (HHMM, local time)All of these came up during the real build, in this order.
| Message | Cause and fix |
|---|---|
Board doesn't appear on the network after flashing | Wrong Wi-Fi password, or no location set in Imager, leaving the radio disabled. Re-flash with the location set. |
hci0: can't up device: operation not possible due to RF-kill | Bluetooth soft-blocked. sudo rfkill unblock bluetooth, then the service from step 09 to make it permanent. |
Vehicle is not in range: failed to scan for YOUR_VIN | The placeholder text was left in the URL. Use the real 17-character VIN. |
your public key has not been paired with the vehicle | The key request was sent but never confirmed on the car's screen. Redo the pairing, holding the card against the console for 2–3 seconds. |
dial tcp connection refused on the PV meter | Modbus TCP isn't listening on the inverter. Enable it under Communication → Modbus; verify with nc -zv IP 502. |
For Communication please log in as customer | The inverter's local password. Reset it via PIN + Solar.web, as in step 06. |
Inverter stopped, right after saving settings | Normal restart of the communication side, or simply low light. It returns within a minute or two; Modbus works even when the inverter isn't producing. |
Found systemd service database ... | A notice, not an error. Run the check with --ignore-db. |
Unit voltage-watch.service not found | The service file hadn't been created yet. Create it, then daemon-reload. |
| Mode jumps back to Min+PV after you change it | The first version of the script enforced the state continuously. The version in step 12 only commands on threshold crossings. |
charger out of sync: expected enabled, got disabled (and the reverse) | evcc can’t switch a Tesla Wall Connector directly — every start/stop is a Bluetooth command to the car, while the enabled/disabled reading comes from the charger. When a command doesn’t land — usually a start after the car has slept — the two drift apart and won’t resync until a physical replug (or the app) wakes the car. Minimise it by pausing less: the disable.delay from step 10, a current BLE proxy, and minpv on marginal mornings. A few transient sync warnings around a genuine state change are normal. |
charger logic error: current mismatch … make sure your interval is at least 30s | evcc is changing the charge current faster than the car can apply and confirm it over Bluetooth, so the reported current is always a step behind the commanded one — and the loop over-corrects, making the current hunt and the grid flap. Set interval: 30s or more in /etc/evcc.yaml and restart; 45s is comfortable for BLE. |
start command can’t wake it, so it sits desynced until you replug. The honest tiles from step 15 show this correctly as Idle at 0 W rather than a frozen “charging”; the real fix is upstream — keep it from pausing at all with a generous disable.delay.What to do once everything works.
journalctl -u voltage-watch -f # follow the voltage thresholds
journalctl -u evcc -n 100 --no-pager # recent evcc errors
docker logs -f tesla-ble-http-proxy # traffic to the car
sudo apt update && sudo apt upgrade -y # updates, evcc included/etc/evcc.yaml, the service files (including grid-voltage-logger.service and charge-state-logger.service), the three helper scripts and the monitoring docker-compose.yml — that's everything needed to rebuild the system on a fresh cardWhere evcc's built-in statistics stop, a time-series database and a dashboard begin.
The evcc interface keeps per-session records, but not the fine grain: every phase voltage, every watt, second by second, kept for as long as you like. InfluxDB stores that stream; Grafana draws it. Both run in Docker on the same Pi, next to the Bluetooth bridge from step 08 — no extra hardware, no cloud account.
Docker is already installed from step 08. Put both services in a second stack. The InfluxDB block self-initialises on first start — organisation, bucket, admin user and a fixed token — so there's no setup wizard to click through:
mkdir -p ~/monitoring
nano ~/monitoring/docker-compose.ymlservices:
influxdb:
image: influxdb:2.7
container_name: influxdb
restart: unless-stopped
ports:
- "8086:8086"
volumes:
- influxdb-data:/var/lib/influxdb2
- influxdb-config:/etc/influxdb2
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=admin
- DOCKER_INFLUXDB_INIT_PASSWORD=changeme-strong-pw
- DOCKER_INFLUXDB_INIT_ORG=home
- DOCKER_INFLUXDB_INIT_BUCKET=evcc
- DOCKER_INFLUXDB_INIT_RETENTION=0
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-evcc-token
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- influxdb
volumes:
influxdb-data:
influxdb-config:
grafana-data:cd ~/monitoring
docker compose up -dchangeme-strong-pw and my-super-secret-evcc-token with your own. The token is the only credential evcc uses to write, so treat it like a password. RETENTION=0 keeps data forever — evcc's stream is tiny, but on a Pi that means SD-card writes for years, so a good card or a USB-SSD is the safer home for influxdb-data.Add one top-level block to the configuration file — not nested under site or loadpoints:
influx:
url: http://localhost:8086
database: evcc # v2 quirk: this is the BUCKET name
org: home
token: my-super-secret-evcc-tokendatabase: key, not bucket:. The org and token must match the compose file exactly — a mismatch shows up as unauthorized access in the evcc log and nothing else. Restart with sudo systemctl restart evcc; the interface then lists InfluxDB as configured.Open http://evcc.local:3000 (first login admin / admin). Add the data source, then import the dashboard:
http://influxdb:8086 (the container name resolves inside Docker's network), then the organisation home, the token, and default bucket evcc. Save & test.evcc-grafana-dashboard.json, and select the InfluxDB source when asked.The dashboard leads with the always-on House Voltage (Smart Meter) section, then a Charging — status, reason & live values block, then charge / PV / home power and per-phase current, and finally vehicle and battery state of charge with charged energy per session. An earlier build also carried a separate charger-voltage section, but once the always-on house voltage existed that became redundant — the charger's voltage only appears while the car is drawing current — so it was dropped; the live charging voltage still shows as a tile inside the Charging block.
elements + layout form, where annotations is an array rather than { list: [...] }. An older-schema file still imports fine through Upload JSON file (Grafana migrates it on the way in), but pasting one into Dashboard settings → JSON Model on 13.x throws “expected array” — paste a new-schema export there instead.The complete dashboard, in the Grafana 13.x schema, wired to a DS_INFLUXDB datasource variable. Paste it into Dashboard settings → JSON Model, or drop it in a file and use Upload JSON file — either way, pick your InfluxDB source afterwards.
{
"annotations": [
{
"kind": "AnnotationQuery",
"spec": {
"builtIn": true,
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"query": {
"datasource": {
"name": "-- Grafana --"
},
"group": "grafana",
"kind": "DataQuery",
"spec": {},
"version": "v0"
}
}
}
],
"cursorSync": "Crosshair",
"editable": true,
"elements": {
"panel-12": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: -2m)\n |> filter(fn: (r) => r._measurement == \"chargePower\" and r._field == \"value\")\n |> last()\n |> map(fn: (r) => ({ r with _value: if r._value > 100.0 then 1.0 else 0.0 }))"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 12,
"links": [],
"title": "Charging now?",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"0": {
"index": 0,
"text": "Idle"
},
"1": {
"index": 1,
"text": "CHARGING"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#5A6370",
"value": 0
},
{
"color": "green",
"value": 1
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-13": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: -5m)\n |> filter(fn: (r) => r._measurement == \"chargeState\" and r._field == \"mode\")\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 13,
"links": [],
"title": "Reason (why)",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"0": {
"index": 0,
"text": "Not charging"
},
"1": {
"index": 1,
"text": "PV \u00b7 surplus"
},
"2": {
"index": 2,
"text": "Boost \u00b7 high V"
},
"3": {
"index": 3,
"text": "Fast \u00b7 manual"
}
},
"type": "value"
},
{
"options": {
"-1": {
"index": 4,
"text": "\u2014"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#5A6370",
"value": 0
},
{
"color": "green",
"value": 1
},
{
"color": "orange",
"value": 2
},
{
"color": "blue",
"value": 3
}
]
}
},
"overrides": []
},
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-14": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargePower\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 14,
"links": [],
"title": "Power",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "watt"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "value",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-15": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargeCurrents\")\n |> filter(fn: (r) => r._field == \"l1\" or r._field == \"l2\" or r._field == \"l3\")\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 15,
"links": [],
"title": "Current \u2014 per phase",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "amp"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-16": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargeVoltages\")\n |> filter(fn: (r) => r._field == \"l1\" or r._field == \"l2\" or r._field == \"l3\")\n |> filter(fn: (r) => r._value > 0)\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 16,
"links": [],
"title": "Voltage \u2014 per phase",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
},
"unit": "volt"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-17": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargeState\")\n |> filter(fn: (r) => r._field == \"reason\")\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 17,
"links": [],
"title": "When & why the Tesla charged",
"vizConfig": {
"group": "state-timeline",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 85,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
"mappings": [
{
"options": {
"0": {
"index": 0,
"text": "Not charging"
},
"1": {
"index": 1,
"text": "PV \u00b7 surplus"
},
"2": {
"index": 2,
"text": "Boost \u00b7 high V"
},
"3": {
"index": 3,
"text": "Fast \u00b7 manual"
}
},
"type": "value"
},
{
"options": {
"-1": {
"index": 4,
"text": "\u2014"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#5A6370",
"value": 0
},
{
"color": "green",
"value": 1
},
{
"color": "orange",
"value": 2
},
{
"color": "blue",
"value": 3
}
]
}
},
"overrides": []
},
"options": {
"alignValue": "center",
"annotations": {
"clustering": -1,
"multiLane": false
},
"legend": {
"displayMode": "list",
"overflow": "ellipsis",
"placement": "bottom",
"showLegend": true
},
"mergeValues": true,
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
}
},
"version": "13.1.1"
}
}
},
"panel-19": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargePower\" or r._measurement == \"pvPower\" or r._measurement == \"homePower\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 19,
"links": [],
"title": "Power \u2014 charge / PV / home",
"vizConfig": {
"group": "timeseries",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"showValues": false,
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "watt"
},
"overrides": []
},
"options": {
"annotations": {
"clustering": -1,
"multiLane": false
},
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"enableFacetedFilter": false,
"overflow": "ellipsis",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "none"
}
}
},
"version": "13.1.1"
}
}
},
"panel-2": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"gridVoltage\")\n |> filter(fn: (r) => r._field == \"l1\")\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 2,
"links": [],
"title": "House L1 (now)",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "orange",
"value": 207
},
{
"color": "green",
"value": 216
},
{
"color": "orange",
"value": 244
},
{
"color": "red",
"value": 255
}
]
},
"unit": "volt"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-20": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargeCurrents\")\n |> filter(fn: (r) => r._field == \"l1\" or r._field == \"l2\" or r._field == \"l3\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 20,
"links": [],
"title": "Charge current per phase (L1 / L2 / L3)",
"vizConfig": {
"group": "timeseries",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 5,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"showValues": false,
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "amp"
},
"overrides": []
},
"options": {
"annotations": {
"clustering": -1,
"multiLane": false
},
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"enableFacetedFilter": false,
"overflow": "ellipsis",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "none"
}
}
},
"version": "13.1.1"
}
}
},
"panel-22": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"vehicleSoc\" or r._measurement == \"batterySoc\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 22,
"links": [],
"title": "State of charge \u2014 vehicle / battery",
"vizConfig": {
"group": "timeseries",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "stepAfter",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"showValues": false,
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "percent"
},
"overrides": []
},
"options": {
"annotations": {
"clustering": -1,
"multiLane": false
},
"legend": {
"calcs": [
"last"
],
"displayMode": "table",
"enableFacetedFilter": false,
"overflow": "ellipsis",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "none"
}
}
},
"version": "13.1.1"
}
}
},
"panel-23": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargedEnergy\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 23,
"links": [],
"title": "Charged energy per session",
"vizConfig": {
"group": "timeseries",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 15,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"showValues": false,
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "kwatth"
},
"overrides": []
},
"options": {
"annotations": {
"clustering": -1,
"multiLane": false
},
"legend": {
"calcs": [
"max"
],
"displayMode": "table",
"enableFacetedFilter": false,
"overflow": "ellipsis",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "none"
}
}
},
"version": "13.1.1"
}
}
},
"panel-24": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"vehicleSoc\" and r._field == \"value\")\n |> filter(fn: (r) => r._value > 0)\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 24,
"links": [],
"title": "Battery",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"decimals": 0,
"max": 100,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "orange",
"value": 20
},
{
"color": "green",
"value": 50
}
]
},
"unit": "percent"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-3": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"gridVoltage\")\n |> filter(fn: (r) => r._field == \"l2\")\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 3,
"links": [],
"title": "House L2 (now)",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "orange",
"value": 207
},
{
"color": "green",
"value": 216
},
{
"color": "orange",
"value": 244
},
{
"color": "red",
"value": 255
}
]
},
"unit": "volt"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-4": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"gridVoltage\")\n |> filter(fn: (r) => r._field == \"l3\")\n |> last()"
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 4,
"links": [],
"title": "House L3 (now)",
"vizConfig": {
"group": "stat",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"decimals": 1,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "orange",
"value": 207
},
{
"color": "green",
"value": 216
},
{
"color": "orange",
"value": 244
},
{
"color": "red",
"value": 255
}
]
},
"unit": "volt"
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}
},
"version": "13.1.1"
}
}
},
"panel-5": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"gridVoltage\")\n |> filter(fn: (r) => r._field == \"l1\" or r._field == \"l2\" or r._field == \"l3\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)"
},
"version": "v0"
},
"refId": "A"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "import \"array\"\narray.from(rows: [\n {_time: v.timeRangeStart, _field: \"limit\", _value: 255.0},\n {_time: v.timeRangeStop, _field: \"limit\", _value: 255.0}\n])"
},
"version": "v0"
},
"refId": "B"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 5,
"links": [],
"title": "House voltage per phase \u2014 Smart Meter (L1 / L2 / L3)",
"vizConfig": {
"group": "timeseries",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 5,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"showValues": false,
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "area"
}
},
"decimals": 1,
"max": 260,
"min": 200,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": 0
},
{
"color": "red",
"value": 255
}
]
},
"unit": "volt"
},
"overrides": [
{
"matcher": {
"id": "byFrameRefID",
"options": "B"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
},
{
"id": "custom.lineWidth",
"value": 4
},
{
"id": "custom.fillOpacity",
"value": 0
},
{
"id": "custom.lineStyle",
"value": {
"fill": "solid"
}
},
{
"id": "custom.pointSize",
"value": 0
},
{
"id": "custom.thresholdsStyle",
"value": {
"mode": "off"
}
},
{
"id": "displayName",
"value": "Derate limit 255 V"
}
]
}
]
},
"options": {
"annotations": {
"clustering": -1,
"multiLane": false
},
"legend": {
"calcs": [
"min",
"mean",
"max"
],
"displayMode": "table",
"enableFacetedFilter": false,
"overflow": "ellipsis",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "none"
}
}
},
"version": "13.1.1"
}
}
}
},
"layout": {
"kind": "RowsLayout",
"spec": {
"rows": [
{
"kind": "RowsLayoutRow",
"spec": {
"collapse": false,
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-2"
},
"height": 5,
"width": 8,
"x": 0,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-3"
},
"height": 5,
"width": 8,
"x": 8,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-4"
},
"height": 5,
"width": 8,
"x": 16,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-5"
},
"height": 9,
"width": 24,
"x": 0,
"y": 5
}
}
]
}
},
"title": "House Voltage \u2014 Smart Meter (always on, at the feed-in point)"
}
},
{
"kind": "RowsLayoutRow",
"spec": {
"collapse": false,
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-12"
},
"height": 5,
"width": 4,
"x": 0,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-24"
},
"height": 5,
"width": 4,
"x": 4,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-13"
},
"height": 5,
"width": 4,
"x": 8,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-14"
},
"height": 5,
"width": 4,
"x": 12,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-15"
},
"height": 5,
"width": 4,
"x": 16,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-16"
},
"height": 5,
"width": 4,
"x": 20,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-17"
},
"height": 6,
"width": 24,
"x": 0,
"y": 5
}
}
]
}
},
"title": "Charging \u2014 status, reason & live values"
}
},
{
"kind": "RowsLayoutRow",
"spec": {
"collapse": false,
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-19"
},
"height": 9,
"width": 12,
"x": 0,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-20"
},
"height": 9,
"width": 12,
"x": 12,
"y": 0
}
}
]
}
},
"title": "Power & Current"
}
},
{
"kind": "RowsLayoutRow",
"spec": {
"collapse": false,
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-22"
},
"height": 9,
"width": 12,
"x": 0,
"y": 0
}
},
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-23"
},
"height": 9,
"width": 12,
"x": 12,
"y": 0
}
}
]
}
},
"title": "State of Charge & Energy"
}
}
]
}
},
"links": [],
"liveNow": false,
"preload": false,
"tags": [
"evcc",
"influxdb",
"energy"
],
"timeSettings": {
"autoRefresh": "30s",
"autoRefreshIntervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"fiscalYearStartMonth": 0,
"from": "now-24h",
"hideTimepicker": false,
"timezone": "browser",
"to": "now"
},
"title": "evcc \u2014 Grid Voltage & Energy",
"variables": [
{
"kind": "DatasourceVariable",
"spec": {
"allowCustomValue": true,
"current": {
"text": "influxdb",
"value": "aft2bbdtzo0lcd"
},
"hide": "dontHide",
"includeAll": false,
"label": "InfluxDB",
"multi": false,
"name": "DS_INFLUXDB",
"options": [],
"pluginId": "influxdb",
"refresh": "onDashboardLoad",
"regex": "",
"skipUrlSync": false
}
}
]
}chargeVoltages, the one voltage it does store, is measured at the Wall Connector and only carries data while the car is drawing current (the flat stretch on the left of the chart before charging starts). The always-on house voltage the voltage-watch service reads in step 12 is used for switching and then discarded. To keep a permanent record of it, a second tiny logger writes it in.This is the voltage that matters for the whole boost story — the level at the feed-in point, climbing through the middle of a sunny day whether or not the car is plugged in. The logger polls the same Solar API endpoint as voltage-watch every 15 seconds and writes a gridVoltage measurement (fields l1/l2/l3) straight into the evcc bucket.
#!/bin/bash
# Always-on: logs the Fronius Smart Meter's phase voltages into InfluxDB,
# whether the car is charging or not. Same Solar API as voltage-watch.
INVERTER="192.168.50.99"
INFLUX="http://localhost:8086"
ORG="home"; BUCKET="evcc"
TOKEN="my-super-secret-evcc-token" # must match the compose file
INTERVAL=15
while true; do
json=$(curl -s --max-time 10 \
"http://$INVERTER/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0")
v1=$(echo "$json" | jq -r '.Body.Data.Voltage_AC_Phase_1 // empty')
v2=$(echo "$json" | jq -r '.Body.Data.Voltage_AC_Phase_2 // empty')
v3=$(echo "$json" | jq -r '.Body.Data.Voltage_AC_Phase_3 // empty')
if [ -n "$v1" ] && [ -n "$v2" ] && [ -n "$v3" ]; then
curl -s --max-time 5 -XPOST \
"$INFLUX/api/v2/write?org=$ORG&bucket=$BUCKET&precision=s" \
-H "Authorization: Token $TOKEN" \
--data-binary "gridVoltage l1=$v1,l2=$v2,l3=$v3" >/dev/null
fi
sleep $INTERVAL
done[Unit]
Description=Log Fronius Smart Meter voltages to InfluxDB
After=network-online.target evcc.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/grid-voltage-logger.sh
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.targetsudo chmod +x /usr/local/bin/grid-voltage-logger.sh
sudo systemctl daemon-reload
sudo systemctl enable --now grid-voltage-logger
journalctl -u grid-voltage-logger -fWith that running, the dashboard's lead block — House Voltage (Smart Meter) — draws continuously, day and night, carrying the thick 255 V red line and the danger-zone highlight. This is the trace that shows the midday climb the boost tier exists to answer, plugged in or not.
array/constant query has the InfluxDB data source selected.To confirm data is landing before building anything, query the bucket directly — in Grafana's Explore tab, or from the Pi:
docker exec influxdb influx query \
'from(bucket:"evcc") |> range(start:-15m)' \
--org home --token my-super-secret-evcc-tokenRows of chargePower, pvPower, batterySoc and the rest mean the loop is closed: evcc measures, InfluxDB remembers, Grafana shows.
The live values — amps, volts, kilowatts — are already in the bucket. What isn't is the reason the car was charging: evcc knows its own mode (PV surplus, Min+PV boost, fast) but, like the meter voltage, never writes it down. A second small logger reads evcc's API and records it, so the dashboard can colour the day by why charging happened — and, crucially, show the step-12 voltage trigger firing as an orange band.
It writes a chargeState measurement with three fields: mode (0 off / 1 pv / 2 minpv / 3 now), charging (0/1), and reason — the mode code while actually charging, otherwise 0. That single reason signal drives a state-timeline: grey when idle, green on surplus, orange on the high-voltage boost, blue on a manual fast charge.
#!/bin/bash
# Logs the loadpoint's mode + charging state, so the dashboard shows
# WHEN the Tesla charged and WHY. Reads evcc's own API.
EVCC="http://localhost:7070"
INFLUX="http://localhost:8086"
ORG="home"; BUCKET="evcc"
TOKEN="my-super-secret-evcc-token" # must match the compose file
LP=0 # loadpoint index (0 = first)
INTERVAL=10
while true; do
state=$(curl -s --max-time 8 "$EVCC/api/state")
mode=$(echo "$state" | jq -r ".loadpoints[$LP].mode // empty")
charging=$(echo "$state" | jq -r ".loadpoints[$LP].charging // empty")
case "$mode" in
off) m=0 ;; pv) m=1 ;; minpv) m=2 ;; now) m=3 ;; *) m=-1 ;;
esac
if [ "$charging" = "true" ]; then c=1; else c=0; fi
if [ "$c" = "1" ] && [ "$m" -ge 0 ]; then r=$m; else r=0; fi
if [ -n "$mode" ]; then
curl -s --max-time 5 -XPOST \
"$INFLUX/api/v2/write?org=$ORG&bucket=$BUCKET&precision=s" \
-H "Authorization: Token $TOKEN" \
--data-binary "chargeState mode=$m,charging=$c,reason=$r" >/dev/null
fi
sleep $INTERVAL
done[Unit]
Description=Log evcc loadpoint mode and charging state to InfluxDB
After=network-online.target evcc.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/charge-state-logger.sh
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.targetsudo chmod +x /usr/local/bin/charge-state-logger.sh
sudo systemctl daemon-reload
sudo systemctl enable --now charge-state-loggerThe Charging — status, reason & live values section then lights up: a row of six equal tiles — a Charging tile that flips to CHARGING in green, a Battery tile showing the Tesla's state of charge (coloured red / orange / green by level), a Reason tile reading “PV · surplus”, “Boost · high V” or “Fast · manual”, then live kW, A and V — and, underneath, the state-timeline painting every charging window in the colour of the logic that drove it.
charging flag means “the loadpoint is enabled and commanding a charge”, which is not the same as current actually flowing — so the Charging and Reason tiles are keyed off real power instead. The Charging query reduces chargePower to 1 above 100 W and 0 below (so 300 mA of standby reads Idle); the Reason query reads the loadpoint mode directly (1 PV / 2 Boost / 3 Fast / 0 off), which the logger records every cycle regardless of the charging flag — so it stays truthful even when evcc drifts out of sync (step 13). An earlier version tried to pivot mode against chargePower, but the two measurements carry different tags and the merge returned no rows (“No data”); reading mode alone is simpler and robust. It does need the logger to write a mode field — older builds only wrote reason, so add mode=$m to its InfluxDB write line. The Battery tile drops vehicleSoc == 0 and keeps the last non-zero reading, so it holds the real charge level while the car sleeps and evcc can’t reach it over Bluetooth, instead of a red 0 %.charge-state-logger service, while power, current, voltage and PV come straight from evcc. A tile stuck on an old value while the graphs stay live means that logger has stopped — sudo systemctl restart charge-state-logger. Confirm all three helpers are enabled so they return after a power cut: systemctl is-enabled voltage-watch charge-state-logger grid-voltage-logger.The radial "Current Power" widget from Solar.web, rebuilt on your own data — and improved, because the Tesla gets its own node.
Everything the Fronius Current Power circle shows — PV production, grid exchange and house draw — is already in the evcc bucket, so the animated flow can be rebuilt in Grafana without touching the inverter. The Solar Flow panel plugin (lehmannch-flow-panel) draws a central hub with particle-style flows out to Solar, Grid and Load, plus up to three extra nodes — one of which becomes the Tesla, something the real Fronius view can't show.
This build keeps nothing external in the command path, and the Pi sits on the isolated IoT segment (step 02), so the Grafana container can't reach grafana.com. That rules out the usual online routes: grafana-cli plugins install and the GF_INSTALL_PLUGINS environment variable both download at run time and simply time out here. Instead, side-load the plugin — fetch the signed zip on a machine that does have internet, carry it across, and drop it into Grafana's plugin folder inside the grafana-data volume.
curl -L -o solar-flow.zip \
"https://grafana.com/api/plugins/lehmannch-flow-panel/versions/latest/download"unzip solar-flow.zip # -> folder: lehmannch-flow-panel/
docker cp lehmannch-flow-panel grafana:/var/lib/grafana/plugins/
docker exec -u root grafana chown -R grafana:grafana /var/lib/grafana/plugins
docker restart grafanaConfirm the folder landed — it must contain a plugin.json:
docker exec grafana ls /var/lib/grafana/plugins | grep -i lehmannallow_loading_unsigned_plugins required. Because the files live in the grafana-data volume they survive restarts and even a docker compose up -d recreate; only docker compose down -v would wipe them.The panel picks each node by field name, so every query renames its value to a distinct label. All read the latest point (last()) over the dashboard's time range:
from(bucket: "evcc")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "pvPower" and r._field == "value")
|> last()
|> keep(columns: ["_time", "_value"])
|> rename(columns: {_value: "solar"})The other four are identical but for the measurement and the alias:
| Node | Query | _measurement | rename value to |
|---|---|---|---|
| Solar | A | pvPower | solar |
| Grid | B | gridPower | grid |
| Load (house) | C | homePower | load |
| Tesla (power) | D | chargePower | ev |
| Tesla (SoC) | E | vehicleSoc | soc |
Map the field names to nodes, then tune the look. These are the exact settings behind the panel in this build:
| Option | Value | Why |
|---|---|---|
| Solar / Grid / Load | solar / grid / load | the three core nodes |
| Additional sources | 1 → value ev, SoC soc | the Tesla, with its charge level in the ring |
| Source label / icon | Tesla · EV icon · Always show | keeps the car's node even at 0 W |
| Source colour | violet rgb(140, 82, 255) | so the Tesla isn't the same blue as the Load node |
| Measurement unit | Auto | evcc logs watts; Auto renders 6090 as "6.09 kW" |
| Padding | 5 | shrinks the margin so the circles fill the panel |
| Show legend | on | labels each node — Solar, Load, Grid, Tesla |
| Animation speed reference | 150 | lower value = faster particles, so the flow reads at a glance |
| Energy threshold | 5 | draws the line even for small flows |
padding alone — there is no radius option — so a tall panel with small padding gives the biggest circles, while a full-width panel leaves side gutters that shrink the diagram. There's no line-thickness control either, so the flow is emphasised through animation speed and a bright linesColor instead.gridPower positive when importing and negative when exporting — the panel uses that to point the arrow, so exporting about 785 W reads as roughly -785. If the arrow ever looks reversed, flip it with |> map(fn: (r) => ({r with _value: r._value * -1.0})). Leave the battery node off — this build defines only grid and PV meters, so there is no battery data, and homePower already excludes the car, so Load and the Tesla never double-count.Rather than a separate dashboard, the panel is one more element in the model from step 15. On the Grafana 13.x schema that means a Panel under elements and a one-item row in layout. Paste both into Dashboard settings → JSON Model — put the row first so the flow leads the page — then save.
"panel-30": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pvPower\" and r._field == \"value\")\n |> last()\n |> keep(columns: [\"_time\", \"_value\"])\n |> rename(columns: {_value: \"solar\"})"
},
"version": "v0"
},
"refId": "A"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"gridPower\" and r._field == \"value\")\n |> last()\n |> keep(columns: [\"_time\", \"_value\"])\n |> rename(columns: {_value: \"grid\"})"
},
"version": "v0"
},
"refId": "B"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"homePower\" and r._field == \"value\")\n |> last()\n |> keep(columns: [\"_time\", \"_value\"])\n |> rename(columns: {_value: \"load\"})"
},
"version": "v0"
},
"refId": "C"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"chargePower\" and r._field == \"value\")\n |> last()\n |> keep(columns: [\"_time\", \"_value\"])\n |> rename(columns: {_value: \"ev\"})"
},
"version": "v0"
},
"refId": "D"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "${DS_INFLUXDB}"
},
"group": "influxdb",
"kind": "DataQuery",
"spec": {
"query": "from(bucket: \"evcc\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"vehicleSoc\" and r._field == \"value\")\n |> last()\n |> keep(columns: [\"_time\", \"_value\"])\n |> rename(columns: {_value: \"soc\"})"
},
"version": "v0"
},
"refId": "E"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "Fronius-style live power flow (Solar Flow panel plugin).",
"id": 30,
"links": [],
"title": "Live power flow",
"vizConfig": {
"group": "lehmannch-flow-panel",
"kind": "VizConfig",
"spec": {
"fieldConfig": {
"defaults": {},
"overrides": []
},
"options": {
"solarQuery": "solar",
"gridQuery": "grid",
"loadQuery": "load",
"measurementUnit": "auto",
"additionalSourceCount": 1,
"additionalSourceLoadQuery": "ev",
"additionalSourceSOCQuery": "soc",
"additionalSourceLabel": "Tesla",
"additionalSourceIcon": "evPanel",
"additionalSourceColor": "rgb(140, 82, 255)",
"additionalSourceAlwaysShow": true,
"showEnergyThreshold": 5,
"animationSpeedReference": 150,
"padding": 5,
"showLegend": true,
"solarColor": "rgb(244, 174, 1)",
"loadColor": "rgb(0, 141, 209)",
"gridColor": "rgb(128, 128, 128)",
"linesColor": "rgb(64, 196, 255)"
}
},
"version": "2.0.2"
}
}
}{
"kind": "RowsLayoutRow",
"spec": {
"collapse": false,
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-30"
},
"height": 18,
"width": 24,
"x": 0,
"y": 0
}
}
]
}
},
"title": "Live power flow (Fronius-style)"
}
}The result is the four-node diagram that now leads the dashboard: Solar at the top, Load to the left, Grid to the right, and the Tesla below in violet with its charge level in the ring — power flowing from sun to house and car, and the surplus onward to the grid.
Fill in the right-hand column with your own values and you have a map of the whole system.
| Element | Address | Role |
|---|---|---|
| Fronius GEN24 | 192.168.50.99 | Modbus TCP 502, Solar API |
| Tesla Wall Connector | _______________ | HTTP /api/1/vitals |
| Raspberry Pi | evcc.local | host for all services |
| evcc interface | evcc.local:7070 | control and statistics |
| Bluetooth bridge | evcc.local:8080 | dashboard and API to the car |
| InfluxDB | evcc.local:8086 | time-series store, bucket evcc |
| Grafana | evcc.local:3000 | dashboards |
| VIN | _________________ | identifies the car to the bridge |
| Service | What it does | Healthy state |
|---|---|---|
evcc | energy management | active (running) |
voltage-watch | switching to the boost tier | active (running) |
grid-voltage-logger | logs Smart Meter voltage to InfluxDB | active (running) |
charge-state-logger | logs charging mode & reason to InfluxDB | active (running) |
bt-unblock | unblocks the radio at boot | active (exited) |
docker → tesla-ble-http-proxy | commands to the car | Up |
docker → influxdb | stores the history | Up |
docker → grafana | draws the dashboards | Up |
The finished dashboard, led by a Fronius-style power-flow diagram and every panel fed from InfluxDB — with the status tiles now reading real power rather than evcc’s intent. Caught mid-act on PV surplus: solar is making 6.2 kW, the Model 3 is pulling 2.3 kW at 72 %, the house takes the rest and a little spills back to the grid, while L2 and L3 ride near 249 V — close to the derate line the boost tier exists to answer.