Metadata-Version: 2.4
Name: pyocmf
Version: 0.4.0
Summary: Python library for parsing and verifying OCMF (Open Charge Metering Format) signatures from electric vehicle charging stations
Keywords: ocmf,ev,charging,metering,electric-vehicle,signature-verification,eichrecht
Author: Paul Wullenweber
Author-email: Paul Wullenweber <44506902+paul-ww@users.noreply.github.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: phonenumbers>=9.0
Requires-Dist: pydantic-extra-types>=2.10
Requires-Dist: pydantic>=2.10
Requires-Dist: pyocmf[cli,crypto] ; extra == 'all'
Requires-Dist: rich>=14.0 ; extra == 'cli'
Requires-Dist: typer>=0.20 ; extra == 'cli'
Requires-Dist: cryptography>=44 ; extra == 'crypto'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/paul-ww/pyocmf
Project-URL: Documentation, https://paul-ww.github.io/pyocmf/
Project-URL: Repository, https://github.com/paul-ww/pyocmf
Project-URL: Issues, https://github.com/paul-ww/pyocmf/issues
Provides-Extra: all
Provides-Extra: cli
Provides-Extra: crypto
Description-Content-Type: text/markdown

# PyOCMF

[![PyPI version](https://img.shields.io/pypi/v/pyocmf)](https://pypi.org/project/pyocmf/)
[![Python versions](https://img.shields.io/pypi/pyversions/pyocmf)](https://pypi.org/project/pyocmf/)
[![CI](https://github.com/paul-ww/pyocmf/actions/workflows/test.yml/badge.svg)](https://github.com/paul-ww/pyocmf/actions/workflows/test.yml)

Python library for parsing, validating, and verifying OCMF (Open Charge Metering Format) signatures from electric vehicle charging stations.

> **Note**: This is an unofficial library that implements parts of the [OCMF specification](https://github.com/SAFE-eV/OCMF-Open-Charge-Metering-Format). It is not affiliated with or endorsed by [S.A.F.E. e.V.](https://www.safe-ev.de/). For official verification of charging session data, please use the [Transparenzsoftware](https://www.safe-ev.de/de/transparenzsoftware.php) provided by S.A.F.E. e.V. This library may be incomplete or contain discrepancies from the official specification.

**[Try PyOCMF in your browser](https://paul-ww.github.io/pyocmf/demo/)** - No installation required! Paste a record, open an XML file or scan a QR code, and check it locally in your web browser using [Pyodide](https://github.com/pyodide/pyodide).

## Features

- Parse OCMF strings into validated Python objects
- Verify cryptographic signatures for data integrity
- Support for ECDSA with multiple curves (secp256k1, secp192r1, secp256r1, secp384r1, secp521r1, brainpool variants)
- Type-safe models using Pydantic
- Accepts every record the Transparenzsoftware accepts, reports spec deviations as warnings, and offers an optional strict mode
- Eichrecht compliance checks aligned with the Transparenzsoftware

## Installation

```bash
pip install pyocmf[all]
```

This installs the complete package with CLI tools and cryptographic signature verification.

<details>
<summary>Alternative installation options</summary>

```bash
# Minimal (parsing only, no CLI or crypto)
pip install pyocmf

# With CLI only
pip install pyocmf[cli]

# With crypto only
pip install pyocmf[crypto]
```

</details>

## Quick Start

```python
from pyocmf import OCMF

# Parse an OCMF string
ocmf_string = 'OCMF|{"FV":"1.0","GI":"KEBA_KCP30",...}|{"SD":"3045..."}'
ocmf = OCMF.from_string(ocmf_string)

# Access payload data
print(ocmf.payload.GI)  # Gateway ID: "KEBA_KCP30"
print(ocmf.payload.GS)  # Gateway serial number
print(ocmf.payload.RD)  # List of meter readings

# Verify signature (requires pyocmf[crypto])
is_valid = ocmf.verify_signature(public_key_hex)

# Strings with the key appended (OCMF|payload|signature|key), as in QR codes for the
# Transparenzsoftware, verify without one; parsing them emits a SpecWarning
is_valid = OCMF.from_string(ocmf_with_key).verify_signature()
```

## Command Line Interface

```bash
# Validate an OCMF string
ocmf 'OCMF|{"FV":"1.0",...}|{"SD":"3045..."}'

# Validate and verify signature
ocmf 'OCMF|{...}|{...}' --public-key 3059301306072A8648CE3D...

# Validate from XML file (extracts public key automatically)
ocmf charging_session.xml
```

## More Examples

<details>
<summary>Working with XML files</summary>

OCMF data is often distributed in XML format from CPO backends.

```python
from pyocmf import OcmfContainer

container = OcmfContainer.from_xml("charging_session.xml")

for entry in container:
    print(f"Gateway: {entry.ocmf.payload.GI}")
    if entry.public_key:
        is_valid = entry.verify_signature()
        print(f"Signature: {'Valid' if is_valid else 'Invalid'}")
```

</details>

<details>
<summary>Eichrecht compliance checking</summary>

Validate German calibration law requirements for charging transactions.

```python
from pyocmf import OCMF

ocmf_begin = OCMF.from_string(begin_string)
ocmf_end = OCMF.from_string(end_string)

issues = ocmf_begin.check_eichrecht(ocmf_end, errors_only=True)
if not issues:
    print("Transaction is Eichrecht compliant")

# A single record holding both the begin and the end reading is checked the same way
issues = OCMF.from_string(record_string).check_eichrecht(errors_only=True)
```

The checks follow the Transparenzsoftware. They compare the billing-relevant begin and end
readings (loss-compensated registers take precedence) and report errors for a meter status
other than OK, an energy error flag, decreasing values or timestamps, mismatching OBIS
codes, units or serial numbers, and invalid identification levels. Time synchronization,
time error flags, cable loss and identification data mismatches are reported as warnings.

</details>

<details>
<summary>Public key metadata</summary>

Extract structured metadata from public keys per OCMF spec Table 23.

```python
from pyocmf import PublicKey

public_key = PublicKey.from_string(public_key_hex)
print(f"Curve: {public_key.curve}")
print(f"Key Size: {public_key.size} bits")

# Check if key matches signature algorithm
matches = public_key.matches_signature_algorithm(ocmf.signature.SA)
```

</details>

## Supported Signature Algorithms

PyOCMF supports the ECDSA signature algorithms defined in the OCMF specification:

- **secp192k1**, **secp256k1** - Koblitz curves
- **secp192r1**, **secp256r1**, **secp384r1**, **secp521r1** - NIST curves  
- **brainpool256r1**, **brainpoolP256r1**, **brainpool384r1** - Brainpool curves
- **SHA256** and **SHA512** hash functions

secp192k1 is defined by the specification but cannot be verified, because the
`cryptography` package does not support this curve.

## Error Handling

```python
from pyocmf import OCMF, SignatureVerificationError, OcmfFormatError

try:
    ocmf = OCMF.from_string(ocmf_string)
    is_valid = ocmf.verify_signature(public_key)
except OcmfFormatError as e:
    print(f"Invalid OCMF format: {e}")
except SignatureVerificationError as e:
    print(f"Signature verification error: {e}")
```

All library errors derive from `PyOCMFError`.

## Spec Deviations

pyocmf accepts every record the [Transparenzsoftware](https://www.safe-ev.de/de/transparenzsoftware.php)
accepts. Values outside the OCMF specification, such as unknown codes, exceeded lengths
or missing mandatory fields, are kept as-is and reported as a `SpecWarning` instead of
failing to parse:

```python
import warnings

from pyocmf import OCMF, OcmfContainer, SpecWarning

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always", SpecWarning)
    ocmf = OCMF.from_string(ocmf_string)

for warning in caught:
    print(warning.message)
```

Pass `strict=True` to reject non-compliant records instead. The deviation is raised as
the usual `OcmfPayloadError` or `OcmfSignatureError`:

```python
ocmf = OCMF.from_string(ocmf_string, strict=True)
container = OcmfContainer.from_xml("transaction.xml", strict=True)
```

On the command line, use `--strict`.

## Development

```bash
# Clone the repository with its submodules (OCMF spec and Transparenzsoftware test data)
git clone --recursive https://github.com/paul-ww/pyocmf.git
cd pyocmf

# Install dependencies with uv
uv sync

# Run tests
uv run pytest

# Run type checking
uv run ty check src test

# Run linting
uv run ruff check .
```

See [AGENTS.md](AGENTS.md) for project conventions, including Transparenzsoftware parity.

## Documentation

Full documentation including API reference is available at [paul-ww.github.io/pyocmf](https://paul-ww.github.io/pyocmf/).

## License

See LICENSE file for details.

## About OCMF

OCMF (Open Charge Metering Format) is a standardized format for metering data from electric vehicle charging stations. It ensures transparency and tamper-proof documentation of charging sessions, complying with legal requirements such as the EU Measuring Instruments Directive (MID) and German Eichrecht.

For more information about OCMF, visit [safe-ev.de](https://www.safe-ev.de/).

---

<p align="center"><i>Vibe-engineered with <a href="https://claude.ai/code">Claude Code</a></i> 🤖</p>
