Skip to content
binary-butterflyPublic

About

Open ChargePoint DataBase

Resources

Stars

11 stars

Watchers

2 watching

Forks

Latest commit

 

History

683 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Open ChargePoint DataBase - OCPDB

This application aggregates chargepoint data from different sources and provices it without authentication using an OCPI-style JSON-API and a vector tile server.

Data sources

Mobilithek sources were last validated on 2026-10-28 with dev/mobilithek_helper/validate_datex2.py. DATEX II data has to be valid against the DATEX II schema, otherwise the import fails as a whole. See Mobilithek helper scripts for details. Problems of dysfunctional sources were re-checked on 2026-11-01 and reported at datex2-publications-issues.

name uid realtime credentials comment
Bundesnetzagentur: API bnetza_api false false Additional config ignore_operators:: list[str] is supported, which will ignore given operators during import. Set to weekly download, as it does not change so often.
Bundesnetzagentur: Excel bnetza_excel false false
chargecloud: Stadtwerke Ludwigsburg chargecloud_ludwigsburg true true
chargecloud: Stadtwerke Pforzheim chargecloud_pforzheim true false
chargecloud: Stadtwerke Stuttgart chargecloud_stuttgart true false
chargecloud: Stadtwerke Tübingen chargecloud_tuebingen true false
Datex2: AH Beteiligungsgesellschaft datex2_ah_beteiligung true true
Datex2: Ampeco datex2_ampeco true true Static data is schema-invalid (messageContainer, service providers, operating hours), see #25
Datex2: Audi charging hub datex2_audi true true Realtime data is empty, see #26
Datex2: Chargecloud datex2_chargecloud true true
Datex2: e-clearing.net datex2_e_clearing_net true true
Datex2: EAAZE datex2_eaaze true true
Datex2: EcoMovement datex2_ecomovement true true
Datex2: Elu Mobility datex2_elu_mobility true true Static data is schema-invalid (pointLocation instead of locPointLocation), see #27
Datex2: EnBW datex2_enbw true true
Datex2: ENERANDO datex2_enerando true true
Datex2: enio datex2_enio true true
Datex2: eRound datex2_eround true true
Datex2: Eulektro datex2_eulektro true true
Datex2: EV Price datex2_ev_price true true
Datex2: EWE datex2_ewe true true
Datex2: Flavia datex2_flavia true true
Datex2: GLS Mobility datex2_giroe true true Realtime data is a delta pull and was empty on 2026-10-29, so EVSE matching is unverified
Datex2: GP JOULE Connect datex2_gp_joule true true
Datex2: Grid&Co datex2_gridandco true true Static data is schema-invalid (external identifier without identifier), see #28
Datex2: Inergia datex2_inergia true true Via Mobilithek
Datex2: Inselwerke datex2_inselwerke true true
Datex2: K.u.L. Rudnick datex2_kul_rudnick false true No data published, see #29
Datex2: ladebusiness datex2_ladebusiness true true Data offer was withdrawn
Datex2: Ladesonne datex2_ladesonne true true
Datex2: Lichtblick datex2_lichtblick true true
Datex2: Midorion datex2_midorion true true
Datex2: Monta datex2_monta true true Static data is schema-invalid (times without seconds and offset), see #30
Datex2: msu datex2_msu true true
Datex2: Pump datex2_pump false true Static data is schema-invalid (versionG at energy rates), see #31
Datex2: Qwello datex2_qwello true true
Datex2: R & M Beteiligungsgesellschaft datex2_r_m_beteiligung true true
Datex2: Road datex2_road true true
Datex2: Smatrics datex2_smatrics true true
Datex2: Sonnenstrom Saarpfalz datex2_sonnenstrom_saarpfalz true true
Datex2: Taubert Consulting datex2_taubert true true
Datex2: Tesla datex2_tesla true true
Datex2: Ulm datex2_ulm true true
Datex2: Vaylens datex2_vaylens true true Static data is schema-invalid (times without seconds and offset), see #32
Datex2: Virta datex2_virta true true
Datex2: Volkswagen Charging Group datex2_volkswagen true true Realtime data is outdated since 2026-10-02 and does not match static data, see #33
Datex2: Wirelane datex2_wirelane true true
eliso eliso true true Custom JSON format instead of DATEX II, see #34
Giro-e giroe true true
Heilbronn Heckarbogen heilbronn_neckarbogen true true
Lichtblick lichtblick true true Currently dysfunctional
OCHP: Albwerk ochp_albwerk true true
OCHP: Ladenetz ochp_ladenetz true true
OCPI: Stadtnavi ocpi_stadtnavi true false
OpenData Swiss opendata_swiss true false
PBW eaaze_pbw true true

Mobilithek helper scripts

dev/mobilithek_helper contains scripts to manage the Mobilithek subscriptions of the charging data sources. Managing subscriptions needs a user login, so the scripts read MOBILITHEK_USER and MOBILITHEK_PASSWORD from .env.

python dev/mobilithek_helper/list_charging_offers.py   # all charging data offers with subscription and import service
python dev/mobilithek_helper/list_subscriptions.py     # all subscriptions of the account
python dev/mobilithek_helper/subscribe.py <data offer id> [<data offer id> ...]
python dev/mobilithek_helper/unsubscribe.py <subscription id> [<subscription id> ...]
python dev/mobilithek_helper/send_reminder.py [--dry-run] --to <email> <data offer id> [<data offer id> ...]

send_reminder.py sends a German plain text email to the contact person of a provider, asking to approve pending subscriptions. The Mobilithek does not expose contact addresses of other organisations, so the recipient has to be given explicitly. It reads SMTP_HOST, SMTP_PORT (465 uses SSL, otherwise STARTTLS), SMTP_USER, SMTP_PASSWORD and SMTP_FROM from .env.

validate_datex2.py validates the data of a subscription against the DATEX II v3.5 and v3.7 import services without touching the database. It shows which base class fits, why an import fails and how many realtime EVSE statuses match static EVSEs. It needs the webapp and the Mobilithek machine certificate in files/, so it runs in the flask container:

docker compose run --rm flask python dev/mobilithek_helper/validate_datex2.py <static subscription id> [<realtime subscription id>]

Duplicate matching

There is a matching algorithm which matches live data sources with bnetza sources. You can find details at our matching docs.

API documentation

At api.ocpdb.de you will find an OpenAPI documentation of public endpoints you can use.

Command line interface

The application provides a simple command line interface. You can access any cli command from within the container. The makefile provides a shortcut to run the cli:

make docker-run CMD="flask db upgrade"

Import all sources

flask import all

Import source: static data

flask import static example_source

Import source: realtime data

flask import realtime example_source

Import source: images

flask import images example_source

List all sources

flask source list

Delete source

flask source delete example_source

Matching

flask match run

DATEX II push to Mobilithek

flask location push-mobilithek-static          # Full static DATEX II snapshot
flask location push-mobilithek-realtime-full   # Full realtime status snapshot
flask location push-mobilithek-realtime-diff   # Incremental realtime status diff

See DATEX II push to Mobilithek for details.

DATEX II push to Mobilithek

In addition to importing DATEX II data via Mobilithek (see the datex2_* data sources above), OCPDB can also publish its aggregated dataset back to the Mobilithek in DATEX II format. Three kinds of push are supported:

  • Static snapshot – the full set of locations, charging stations, EVSEs, connectors, tariffs and operators.
  • Realtime full snapshot – the current EVSE status of all locations (a SNAPSHOT_PUSH).
  • Realtime diff – an incremental DELTA_PUSH containing only EVSEs whose status changed since the last push. The last successful push timestamp is stored in Redis (last_datex_realtime_push); if nothing changed, no message is sent but the watermark is still advanced.

Both DATEX II 3.5 and 3.7 are supported, selected via MOBILITHEK_VERSION.

Manual push

The pushes can be triggered manually through the CLI:

flask location push-mobilithek-static          # Full static DATEX II snapshot
flask location push-mobilithek-realtime-full   # Full realtime status snapshot (SNAPSHOT_PUSH)
flask location push-mobilithek-realtime-diff   # Incremental realtime status diff (DELTA_PUSH, skipped when empty)

Scheduled push

When MOBILITHEK_ENABLED is true, the pushes are scheduled automatically as Celery periodic tasks. The schedule is configurable in config.yaml:

MOBILITHEK_ENABLED: true
MOBILITHEK_VERSION: '3.5'              # DATEX II version, '3.5' or '3.7'
MOBILITHEK_NAME: binary butterfly GmbH # supplier name sent in the exchange context
MOBILITHEK_STATIC_PUBLICATION_ID: 980054862909370368
MOBILITHEK_REALTIME_PUBLICATION_ID: 980084361378099200
MOBILITHEK_CERTIFICATE_FILENAME: 'mobilithek-binary-butterfly.crt.pem'  # client certificate in KEY_DIR
MOBILITHEK_KEY_FILENAME: 'mobilithek-binary-butterfly.key.pem'         # client key in KEY_DIR

# Push schedule (only active when MOBILITHEK_ENABLED is true):
MOBILITHEK_STATIC_PUSH_HOUR: 3              # static snapshot once a day at this hour ...
MOBILITHEK_STATIC_PUSH_MINUTE: 0           # ... and minute
MOBILITHEK_REALTIME_FULL_PUSH_FREQUENCY: 21600  # full realtime snapshot, in seconds (default every 6 hours)
MOBILITHEK_REALTIME_DIFF_PUSH_FREQUENCY: 60     # incremental realtime diff, in seconds (default every minute)

Messages are sent via mutual-TLS POST to the Mobilithek publication endpoint, using the client certificate and key located in KEY_DIR.

Official Regional Code support

OCPDB extends the Location data model with a new field official_regional_code in non-strict mode. This field provides the official regional code of a location, if available. Following regional codes are used:

Setup DEU: Regionalschlüssel

In Germany, we use the dataset Verwaltungsgebiete 1:25 000 (VG25) by Bundesamt für Kartographie und Geodäsie (BKG) for assigning official regional codes to locations, licenced as Creative Commons Namensnennung 4.0 International. We download the data using wget, transform the data using ogr2ogr and store it our Postgis database.

The script assumes that a once-only import is sufficient. You must delete the data/regionalschluessel/.vg25-imported "marker file" (and re-run the script) to trigger a re-import.

It also assumes that the data at the VG25 URL is immutable, the data will be downloaded only once. You must delete data/regionalschluessel/vg25.gpkg (and re-run the script) to trigger a re-download. data/regionalschluessel/vg25.gpkg in order to trigger the download again, and data/regionalschluessel/.vg25-imported to trigger the import again. You can use this mechanism for ansible automatization, too: if you drop the geopackage at data/regionalschluessel/vg25.gpkg via ansible, you won't need to download the file during runtime.

You can run

flask location assign-regionalschluessel

to assign regional codes to all locations already in the database. You can limit it to specific locations by providing the source id:

flask location assign-regionalschluessel --source-id 1

Update Regionalschlüssel file

In case of an Regionalschlüssel file update, make sure that the new geopackage has the same format as the old one. Afterwards, you can run

flask location assign-regionalschluessel --re-assign

to re-assign regional codes to all locations.

System requirements and installation

The installation process is documented at INSTALL.md.

Dependency management

This project uses uv for dependency management. All runtime and development dependencies are declared in pyproject.toml, and exact versions are pinned in uv.lock. There are no requirements.txt files anymore.

  • Runtime dependencies live in [project.dependencies].
  • Development / test dependencies live in the dev group under [dependency-groups].
  • The shared flask-openapi and butterfly_pubsub libraries are served from binary butterfly's private package index, which is configured under [tool.uv.sources] / [[tool.uv.index]].

Common commands (all run inside the Docker containers in development, see the Makefile):

# Install all dependencies (incl. dev) into a virtual environment, respecting the lockfile
uv sync

# Install only runtime dependencies (used for the production image)
uv sync --no-dev

# Add / remove a dependency (updates pyproject.toml and uv.lock)
uv add some-package
uv remove some-package

# Upgrade the lockfile to the latest allowed versions
uv lock --upgrade

# Run a command inside the environment
uv run flask import all

After changing dependencies, rebuild the dev image with make docker-build so the containers pick up the new packages.

Logging

The application uses the logging module with some optional extensions. Logging can be configured using the config.yml.

The application provides some additional context and / or special output formats to log entries with custom formatters:

Human readable formatter with injected context

Most requests or tasks have context which can be used in log entries. The simplest is

LOGGING:
  formatters:
    human_readable:
      (): webapp.common.logging.formatter.flask_attributes_formatter.FlaskAttributesFormatter
      format: '%(asctime)s %(levelname)s %(source)s: %(message)s'
      defaults: {'source': '-'}
  handlers:
    my_handler:
      formatter: human_readable

With this example, you add the source to every log entry (if available). Please keep in mind that you need to add a default, because not every log entry has a source_uid context.

Following additional log context variables are available:

  • source
  • initiator
  • location
  • evse
  • image

OpenTelemetry formatter

You can output log entries in OpenTelemetry format, too:

LOGGING:
  formatters:
    open_telemetry:
      (): webapp.common.logging.formatter.flask_open_telemetry_formatter.FlaskOpenTelemetryFormatter
      prefix: ocpdb
      service_name: OCPDB
  handlers:
    my_handler:
      formatter: open_telemetry

Context is automatically injected into log entry Attributes.

Licence

OCPDB is under AGPL. You will find details at the LICENCE.txt.

Participate

We appreciate bug reports and feature requests.

About

Open ChargePoint DataBase

Resources

Stars

11 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages