- Python 97%
- Dockerfile 1.6%
- Makefile 1.4%
|
All checks were successful
/ build-and-push (push) Successful in 8s
LubeLogger computes Distance Traveled exclusively from the Odometer tab (hargata/lubelog#385); fuel records alone leave it at 0. Document the "Automatically Insert Odometer Records" setting in quick start and troubleshooting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| bridge.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
lubelogger-evcc-bridge
A tiny Docker container that automatically imports your finished evcc charging sessions into LubeLogger as fuel (kWh) records — including odometer, cost, solar share, and charge duration.
evcc ──GET /api/sessions──▶ bridge ──POST /api/vehicle/gasrecords/add──▶ LubeLogger
Highlights
- Stateless & idempotent — no database, no volume. Every imported record
carries an invisible
[evcc:…]marker in its notes; the bridge reads those back from LubeLogger to know what's already imported. Wipe the container, restart it, run two copies — nothing gets imported twice. - Zero-config vehicle matching — evcc vehicle names are matched against
your LubeLogger vehicles automatically (
ID.3↔2022 VW ID.3). An explicitVEHICLE_MAPis available for tie-breaking. - Zero dependencies — one Python file on stdlib, ~15 MB Alpine image.
- Dry-run mode so you can see exactly what would be imported first.
Quick start
- In LubeLogger, make sure your EV has the Electric Vehicle switch enabled (so fuel is tracked in kWh), and enable Settings → Automatically Insert Odometer Records — LubeLogger computes Distance Traveled exclusively from the Odometer tab, which stays empty (and shows 0) without it. If your instance requires login, also create an API key: profile icon → Manage API Keys, Editor role.
- Configure and start:
git clone <this repository>
cd lubelogger-evcc-bridge
# edit docker-compose.yml: set EVCC_URL and LUBELOGGER_URL
# (only with auth enabled: cp .env.example .env and set the API key)
docker compose up -d --build
- Watch the first sync:
docker compose logs -f
On the first run the bridge imports your entire evcc session history
(set SYNC_SINCE=2026-01-01 to limit that). Recommended first run:
set DRY_RUN=true, check the log, then flip it off.
Configuration
Everything is configured through environment variables. Every variable also
accepts a _FILE suffix pointing at a file (Docker secrets), e.g.
LUBELOGGER_API_KEY_FILE=/run/secrets/lubelogger_key.
Required
| Variable | Description |
|---|---|
EVCC_URL |
Base URL of evcc, e.g. http://evcc.local:7070 |
LUBELOGGER_URL |
Base URL of LubeLogger, e.g. http://lubelogger:8080 |
If LubeLogger runs without authentication, that's all. Otherwise also set
LUBELOGGER_API_KEY (Editor role) or LUBELOGGER_USERNAME +
LUBELOGGER_PASSWORD (Basic auth).
Optional
| Variable | Default | Description |
|---|---|---|
EVCC_PASSWORD |
– | Only needed if your evcc UI is password-protected |
VEHICLE_MAP |
auto-match | Explicit mapping "<evcc name>=<LubeLogger vehicle id>", comma-separated: "ID.3=1,Zoe=2". Names are compared case/punctuation-insensitively |
DEFAULT_VEHICLE_ID |
– | Also import sessions that have no vehicle assigned in evcc (guest charging) into this LubeLogger vehicle. Unset = skip them |
POLL_INTERVAL |
5m |
How often to check for new sessions (45s, 5m, 1h) |
SYNC_SINCE |
– | Ignore sessions finished before this date (2026-01-01) |
MIN_ENERGY_KWH |
0.1 |
Skip micro-sessions below this energy entirely |
CONSOLIDATE_BELOW_KWH |
2 |
Sessions below this aren't imported on their own — they're merged into the next session's record (summed kWh and cost, all dedup markers kept). Stops solar top-ups from cluttering the list and skewing kWh/100km. 0 disables |
MIN_ECONOMY_KM |
30 |
Records whose odometer advanced less than this since the previous record are marked "missed fuel up", so LubeLogger skips their kWh/100km calc — charging after barely driving otherwise produces absurd economy rows. 0 disables |
ECONOMY_MIN / ECONOMY_MAX |
0 (off) |
Plausibility band for a record's kWh/100km (in your odometer unit). Records whose economy would fall outside it are also marked "missed fuel up" — e.g. 15/40 for a large EV. Catches charge-vs-drive misalignment the distance rule misses |
ODOMETER_FALLBACK |
last |
When evcc has no odometer for a session: last = reuse the highest odometer already in LubeLogger, zero = 0, skip = don't import |
ODOMETER_MULTIPLIER |
1 |
evcc reports km; set 0.621371 if LubeLogger uses miles |
FILL_TO_FULL |
true |
Mark each session as "filled to full". LubeLogger only shows consumption and computes kWh/100km for full fill-ups — with false, partial records roll their kWh into the next full one and the Consumption column stays 0 until then |
MISSED_FUEL_UP |
false |
Mark records as "missed fuel up" |
SOC_MODE |
synthetic |
How to fill LubeLogger's EV state-of-charge fields, which drive its consumption view. synthetic (recommended): the Consumption column shows exactly each session's wallbox-metered charged kWh. session: use evcc's real socStart/socEnd when plausible (implied capacity within 0.5–2× BATTERY_CAPACITY_KWH — vehicle APIs often report stale values), else synthetic. static: always send STARTING_SOC/ENDING_SOC |
CHARGE_LIMIT |
100 |
Your usual charge target in % (e.g. 90). Used as the synthetic ending SoC so records reflect your actual charging habit; any constant keeps the consumption math exact |
BATTERY_CAPACITY_KWH |
100 |
Your battery's capacity — makes synthetic starting SoC values realistic, and bounds the plausibility check in session mode |
STARTING_SOC / ENDING_SOC |
0 |
Static SoC values, only used with SOC_MODE=static |
PRICE_PER_KWH |
– | Compute cost from energy when evcc reports no price (e.g. 0.25) |
CURRENCY_SYMBOL |
€ |
Only used in the notes text |
TAGS |
evcc |
Tags applied to imported records |
LUBELOGGER_DECIMAL |
auto |
Decimal format LubeLogger's server locale expects (dot, comma, or auto). LubeLogger parses 5.52 as 552 under a comma locale; auto verifies the first write by reading the record back and self-corrects |
RECONCILE |
true |
Re-check imported records against evcc each sync and update them when the session data changed — evcc extends finished sessions retroactively (solar pause/resume) and can correct prices |
DRY_RUN |
false |
Log what would be imported without writing anything |
RUN_ONCE |
false |
Do one sync and exit (for cron / systemd timers) |
LOG_LEVEL |
INFO |
DEBUG shows per-session skip reasons |
TZ |
UTC | Timezone used for record dates (e.g. Europe/Amsterdam) |
How records look in LubeLogger
Each charging session becomes one fuel record:
| LubeLogger field | Source |
|---|---|
| Date | evcc session finish time (in your TZ) |
| Odometer | evcc odometer (needs a vehicle integration in evcc), else ODOMETER_FALLBACK |
| Fuel consumed | chargedEnergy (kWh) |
| Cost | evcc session price, else PRICE_PER_KWH × energy, else 0 |
| Notes | loadpoint, solar %, duration, avg price + the dedup marker |
| Tags | TAGS |
Don't delete the
[evcc:…]marker from a record's notes — it's how the bridge knows that session was already imported.
Vehicle matching
The bridge fetches your LubeLogger vehicles and matches evcc vehicle names
against "<year> <make> <model>", ignoring case and punctuation. Exact
matches win; otherwise a unique substring match is accepted (Zoe matches
2019 Renault Zoe). If a name is ambiguous or unknown, the bridge logs a
warning listing your LubeLogger vehicle ids so you can set VEHICLE_MAP.
Running without compose
docker build -t lubelogger-evcc-bridge .
docker run -d --restart unless-stopped --name lubelogger-evcc-bridge \
-e EVCC_URL=http://evcc.local:7070 \
-e LUBELOGGER_URL=http://lubelogger.local:8080 \
-e TZ=Europe/Amsterdam \
lubelogger-evcc-bridge
Troubleshooting
- "No LubeLogger vehicle matched evcc vehicle …" — set
VEHICLE_MAP="<exact evcc name>=<id>"; the warning lists the ids. - Odometer is 0 / stale — evcc only knows the odometer if your vehicle is
configured with a manufacturer integration. Records still import; fix the
odometer in LubeLogger by hand or keep
ODOMETER_FALLBACK=last. - Duplicates after editing notes — the
[evcc:…]marker was removed; delete the duplicate and leave markers intact. - Dashboard shows "Distance Traveled: 0" — LubeLogger derives it from the
Odometer tab only (discussion).
Enable Settings → Automatically Insert Odometer Records so every import
also creates an odometer record. For records imported before enabling it,
add odometer records via
POST /api/vehicle/odometerrecords/add. - HTTP 401 from evcc — set
EVCC_PASSWORD. - Set
LOG_LEVEL=DEBUGto see why individual sessions are skipped.
Development
make test # unit tests (stdlib only)
DRY_RUN=true RUN_ONCE=true EVCC_URL=... LUBELOGGER_URL=... python3 bridge.py
Releasing (maintainers)
Images are versioned by git tag:
- push to
main→:latest+:sha-<commit> - tag
vX.Y.Z→:X.Y.Z,:X.Yand:latest
Unit tests run inside the Dockerfile's test stage, so an image can only
be published when they pass. .forgejo/workflows/build.yml
implements this for a Forgejo instance (kaniko, no docker socket needed);
adapt the IMAGE env and registry credentials (REGISTRY_TOKEN secret with
write:package scope) for your own registry, or use the Makefile:
make release V=0.2.0 # runs tests, tags v0.2.0, pushes → CI publishes
make push-local IMAGE=registry.example.com/you/lubelogger-evcc-bridge