No description
  • Python 97%
  • Dockerfile 1.6%
  • Makefile 1.4%
Find a file
Stefan Schoemaker fcb5a7bfff
All checks were successful
/ build-and-push (push) Successful in 8s
docs: Distance Traveled needs LubeLogger's auto odometer records
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>
2026-08-04 10:05:01 +02:00
.forgejo/workflows ci: keep build context alive across kaniko stages 2026-07-12 11:15:38 +02:00
tests Clamp implausible economy values: ECONOMY_MIN / ECONOMY_MAX 2026-07-14 09:25:56 +02:00
.dockerignore ci: run tests inside the Docker build 2026-07-12 11:04:51 +02:00
.env.example Initial release: evcc → LubeLogger charging session bridge 2026-07-12 10:57:42 +02:00
.gitignore Initial release: evcc → LubeLogger charging session bridge 2026-07-12 10:57:42 +02:00
bridge.py Clamp implausible economy values: ECONOMY_MIN / ECONOMY_MAX 2026-07-14 09:25:56 +02:00
docker-compose.yml Clamp implausible economy values: ECONOMY_MIN / ECONOMY_MAX 2026-07-14 09:25:56 +02:00
Dockerfile Fix HTTP 500 on record add; make repo environment-neutral 2026-07-12 11:25:06 +02:00
LICENSE Fix HTTP 500 on record add; make repo environment-neutral 2026-07-12 11:25:06 +02:00
Makefile Fix HTTP 500 on record add; make repo environment-neutral 2026-07-12 11:25:06 +02:00
README.md docs: Distance Traveled needs LubeLogger's auto odometer records 2026-08-04 10:05:01 +02:00

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 explicit VEHICLE_MAP is 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

  1. 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.
  2. 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
  1. 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=DEBUG to 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.Y and :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