{"id":50515854,"url":"https://github.com/opencpo/opencpo-core","last_synced_at":"2026-06-03T00:01:36.831Z","repository":{"id":362110737,"uuid":"1195527561","full_name":"opencpo/opencpo-core","owner":"opencpo","description":"⚡ OCPP 1.6 + 2.0.1 Central System with zero trust networking, built-in PKI, and profile-driven charger compatibility","archived":false,"fork":false,"pushed_at":"2026-06-02T16:36:17.000Z","size":333,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T17:19:54.677Z","etag":null,"topics":["cpo","ev-charging","fastapi","mtls","ocpp","ocpp16","ocpp201","pki","websocket","zero-trust"],"latest_commit_sha":null,"homepage":"https://opencpo.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/opencpo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T19:14:57.000Z","updated_at":"2026-06-02T16:36:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/opencpo/opencpo-core","commit_stats":null,"previous_names":["opencpo/opencpo-core"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/opencpo/opencpo-core","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencpo%2Fopencpo-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencpo%2Fopencpo-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencpo%2Fopencpo-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencpo%2Fopencpo-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/opencpo","download_url":"https://codeload.github.com/opencpo/opencpo-core/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencpo%2Fopencpo-core/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33841996,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpo","ev-charging","fastapi","mtls","ocpp","ocpp16","ocpp201","pki","websocket","zero-trust"],"created_at":"2026-06-03T00:01:31.787Z","updated_at":"2026-06-03T00:01:36.825Z","avatar_url":"https://github.com/opencpo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OCPP Core\n\nOpen source OCPP 1.6j + 2.0.1 Central System (CSMS) with profile-driven charger compatibility, an event bus architecture, and built-in V2G PKI.\n\n**OCPP 1.6j + 2.0.1 · Built-in PKI/CA · Plug \u0026 Charge (ISO 15118) · OCPI 2.2.1 Roaming · Redis Event Bus · PostgreSQL State**\n\n---\n\n## Features\n\n- **OCPP 1.6j \u0026 2.0.1** — Full WebSocket server (Central System / CSMS)\n- **Profile-driven charger compatibility** — Per-vendor/model behavior adapters, no if/else spaghetti\n- **Built-in PKI** — Own Root CA, CPO Sub-CA, MO Sub-CA. Sign SECC certs and contract certs with zero external dependencies\n- **ISO 15118-2 Plug \u0026 Charge** — Out of the box\n- **OCPI 2.2.1** — CPO-side roaming with EMSPs and other networks\n- **Event Bus** — Redis Streams for real-time data to billing, CRM, dashboards, or any consumer\n- **REST API** — Charger management, session queries, PKI operations\n- **Smart Charging** — SetChargingProfile support for EMS integration\n\n---\n\n## Quick Start\n\n```bash\n# Clone\ngit clone https://github.com/opencpo/opencpo-core.git\ncd opencpo-core\n\n# Start dependencies (Postgres + Redis)\ndocker compose up -d postgres redis\n\n# Install\npython -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\n# Configure\ncp .env.example .env\n# Edit .env — at minimum set PG_PASSWORD\n\n# Run migrations\nalembic upgrade head\n\n# Start\npython main.py\n```\n\nOr run everything with Docker:\n\n```bash\ncp .env.example .env\n# Edit .env as needed\ndocker compose up\n```\n\nChargers connect to:\n- **OCPP 1.6j**: `ws://your-host:9100/ocpp/{charger-id}`\n- **OCPP 2.0.1**: `ws://your-host:9201/ocpp/{charger-id}`\n- **REST API**: `http://your-host:8000`\n\n---\n\n## Architecture\n\n```\nCharger\n  │\n  │  WebSocket (OCPP 1.6j / 2.0.1)\n  ▼\n┌─────────────────────────────────────────┐\n│             OCPP Handler                │\n│   ocpp16/   ·   ocpp201/               │\n│   BootNotification, MeterValues,        │\n│   RemoteStart, SmartCharging, ...       │\n└───────────────┬─────────────────────────┘\n                │\n                │  Profile lookup (vendor/model/firmware → behavior)\n                ▼\n┌─────────────────────────────────────────┐\n│          Charger Profile Registry       │\n│   charger_profiles/                     │\n│   conservative defaults, per-vendor     │\n│   quirks, compliance flags              │\n└───────────────┬─────────────────────────┘\n                │\n        ┌───────┴───────┐\n        │               │\n        ▼               ▼\n  ┌──────────┐    ┌───────────┐\n  │  State   │    │ Event Bus │\n  │  Redis   │    │  Redis    │\n  │ Postgres │    │  Streams  │\n  └──────────┘    └─────┬─────┘\n                        │\n               ┌────────┴────────┐\n               ▼                 ▼\n         Consumer A         Consumer B\n       (billing, CRM)    (dashboard, push)\n```\n\n**Key design principle:** The OCPP handler is charger-agnostic. Vendor-specific behavior lives in `ChargerProfile` objects — the handler consults the profile instead of branching on vendor/model.\n\n---\n\n## Charger Profiles\n\nChargers vary wildly in protocol compliance and behavior quirks. This system lets you describe a charger once and handle it correctly everywhere.\n\n### How it works\n\nOn `BootNotification`, the handler calls `resolve_profile(vendor, model, firmware)` which returns the best matching `ChargerProfile`. This profile is stored in Redis and consulted for all subsequent decisions.\n\n### Built-in profiles\n\n`charger_profiles/examples.py` contains profiles built from real-world field testing:\n\n| Profile ID | Vendor | Notes |\n|---|---|---|\n| `maxpower-ccs2-v3` | MAXPOWER | 60kW DC CCS2, firmware V3.x — with compliance quirks |\n| `hongjiali-120kw` | HONGJIALI | 120kW DC, OCPP 2.0.1 (TBD) |\n| `hongjiali-30kw-portable` | HONGJIALI | 30kW portable DC (TBD) |\n| `generic-ocpp16` | * | Conservative fallback for unknown chargers |\n\n### Adding your own profiles\n\n**Option 1: Register at startup (Python)**\n\n```python\nfrom charger_profiles.registry import register_profile, ChargerProfile\n\nMY_CHARGER = ChargerProfile(\n    id=\"my-charger-v2\",\n    vendor=\"MYVENDOR\",\n    model_pattern=r\"XDC.*\",\n    firmware_pattern=r\"V2\\..*\",\n    description=\"My 50kW DC charger, firmware V2\",\n\n    # Override only what differs from defaults\n    sends_power_measurand=True,\n    power_unit=\"kW\",\n    max_power_kw=50.0,\n    smart_charging_safe=False,  # Known bug: drops WS on SetChargingProfile\n    quirks=[\"Energy in kWh, not Wh\"],\n)\n\nregister_profile(MY_CHARGER)\n```\n\nCall this during app startup, before chargers connect.\n\n**Option 2: YAML file**\n\n```yaml\n# my-profiles.yaml\nprofiles:\n  - id: my-charger-v2\n    vendor: MYVENDOR\n    model_pattern: \"XDC.*\"\n    firmware_pattern: \"V2\\\\..*\"\n    description: \"My 50kW DC charger, firmware V2\"\n    sends_power_measurand: true\n    power_unit: kW\n    max_power_kw: 50.0\n    smart_charging_safe: false\n    quirks:\n      - \"Energy in kWh, not Wh\"\n```\n\n```bash\n# In .env or environment:\nCHARGER_PROFILES_YAML=/etc/ocpp/my-profiles.yaml\n```\n\n**Option 3: Disable built-in examples**\n\nIf you don't want the example profiles loaded:\n```bash\nCHARGER_PROFILES_NO_EXAMPLES=true\n```\n\n---\n\n## Configuration\n\nAll configuration via environment variables. Copy `.env.example` to `.env`.\n\n| Variable | Default | Description |\n|---|---|---|\n| `PG_HOST` | `127.0.0.1` | PostgreSQL host |\n| `PG_PORT` | `5432` | PostgreSQL port |\n| `PG_NAME` | `ocpp` | Database name |\n| `PG_USER` | `ocpp` | Database user |\n| `PG_PASSWORD` | *(required)* | Database password |\n| `REDIS_HOST` | `127.0.0.1` | Redis host |\n| `REDIS_PORT` | `6380` | Redis port |\n| `REDIS_PASSWORD` | | Redis password (optional) |\n| `OCPP16_PORT` | `9100` | OCPP 1.6j WebSocket port |\n| `OCPP201_PORT` | `9201` | OCPP 2.0.1 WebSocket port |\n| `API_PORT` | `8000` | REST API port |\n| `API_KEY` | | API key for REST endpoints (leave empty to disable auth) |\n| `PKI_ORG_NAME` | `OCPP Core` | Organization name in CA certificates |\n| `PKI_ROOT_CA_CN` | `OCPP Core Root CA` | Root CA common name |\n| `PKI_USER_CA_CN` | `OCPP Core User CA` | User CA common name |\n| `PKI_DATA_DIR` | `./data/pki` | PKI storage directory |\n| `OCPI_BASE_URL` | `http://localhost:8000` | Public OCPI base URL |\n| `OCPI_OPERATOR_NAME` | `OCPP Core CPO` | Your CPO name (shown to roaming partners) |\n| `OCPI_OPERATOR_WEBSITE` | | Your website URL |\n| `OCPI_PARTY_ID` | `OCP` | 3-letter OCPI party ID |\n| `OCPI_COUNTRY_CODE` | `NL` | 2-letter country code |\n| `CHARGER_PROFILES_YAML` | | Path to YAML file with custom charger profiles |\n| `CHARGER_PROFILES_NO_EXAMPLES` | `false` | Set to `true` to disable built-in example profiles |\n| `LOG_LEVEL` | `INFO` | Logging level (DEBUG/INFO/WARNING/ERROR) |\n| `LOG_FORMAT` | `json` | Log format (`json` or `text`) |\n\nSee `.env.example` for all options with documentation comments.\n\n---\n\n## Event Bus\n\nEvents are published to Redis Streams. Consumers subscribe and process them asynchronously.\n\n### Implementing a consumer\n\n```python\nfrom events.consumer import EventConsumer\nfrom events.types import EventType\n\nclass MyConsumer(EventConsumer):\n    async def handle(self, event_type: str, payload: dict) -\u003e None:\n        if event_type == EventType.SESSION_STARTED:\n            charge_point_id = payload[\"charge_point_id\"]\n            # do something...\n```\n\nRegister your consumer in `main.py` alongside the existing ones.\n\n### Event types\n\nSee `events/types.py` for all event types: session lifecycle, meter values, charger status changes, alarms, auth events, and more.\n\n---\n\n## Requirements\n\n- Python 3.11+\n- PostgreSQL 16+ (with TimescaleDB extension for meter value hypertables)\n- Redis 7+\n\n---\n\n## License\n\nApache License 2.0 — see [LICENSE](LICENSE).\n\n---\n\n## Contributing\n\nPull requests welcome. When adding support for a new charger model, the most useful contribution is a `ChargerProfile` in `charger_profiles/examples.py` with documented quirks from compliance testing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopencpo%2Fopencpo-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopencpo%2Fopencpo-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopencpo%2Fopencpo-core/lists"}