Levity: An extensible OCPP server and EVSE management platform.
Levity runs well on a t4a.small instance (2GB of RAM)
mkdir levity
cd levity
curl https://raw.githubusercontent.com/keeth/levity/main/docker-compose.full.yml -o docker-compose.yml
# For a local/debug install, you don't need to modify docker-compose.yml.
# For a non-SSL server deployment, edit docker-compose.yml,
# setting DEBUG to false, HOSTNAME to your domain, SECRET_KEY to a unique secret value (see below)
docker compose up -d
docker compose run --rm be poetry run python manage.py migrate
docker compose run --rm be poetry run python manage.py createsuperuser
# Visit your server at port 80, go to /admin to log in as superuser
- Note: Your domain must already resolve to the server IP address for LetsEncrypt to generate certs.
mkdir levity
cd levity
curl https://raw.githubusercontent.com/keeth/levity/main/docker-compose.full-tls.yml -o docker-compose.yml
# Edit docker-compose.yml, setting HOSTNAME to your domain, SECRET_KEY to a unique secret value (see below)
docker compose run --rm --entrypoint 'sh /usr/local/bin/write-nginx-conf.sh example.com' fe_tls
docker compose run --rm --entrypoint 'sh /usr/local/bin/self-signed-cert.sh example.com' fe_tls
docker compose up -d fe
docker compose run --rm --entrypoint 'sh /usr/local/bin/lets-encrypt-cert.sh example.com email@example.com' fe_tls
docker compose restart fe
docker compose up -d
docker compose run --rm be poetry run python manage.py migrate
docker compose run --rm be poetry run python manage.py createsuperuser
# Visit your server at port 443, go to /admin to log in as superuser
- You can generate SECRET_KEY at https://djecrety.ir/
Requirements:
- Docker
- Python 3.11+ w/ Poetry 1.7+
- create virtualenv for
be
, then:
docker compose up -d
cd be/
poetry install
export DJANGO_SETTINGS_MODULE=levity.settings
./manage.py migrate
./manage.py createsuperuser
- create virtualenv for
ws
, then:
cd ws/
poetry install
cd be/
export DJANGO_SETTINGS_MODULE=levity.settings
./manage.py runserver &
./manage.py consume_rpc_queue &
cd ws/
PYTHONPATH=src python main.py
Connect an OCPP-J charge point by configuring it with the Levity websocket URL:
ws://example.com/ws/{charge_point_id}
Some charge points (e.g. Grizzl-e) require a URL with no scheme/protocol, e.g.:
example.com/ws/{charge_point_id}
After connecting, the charge point will be automatically added to the system, and should show up in the admin dashboard:
http://example.com/admin/ocpp/chargepoint/
By default, when a charge point goes into the Preparing state, a RemoteStartTransaction message will be automatically sent to it.
be - the Levity backend, powered by Django (WSGI, synchronous workers)
ws - a websocket server, powered by async Python
fe - nginx frontend which proxies to be and ws
fe-tls - a certbot / LetsEncrypt container which enables TLS and manages certificate renewal
rabbitmq - used for RPC between be and ws
postgres - main database, used by be
In Levity, the websocket server is decoupled from the main backend server. This has several benefits:
- The websocket service can be deployed and scaled independently.
- The websocket service can be deployed at the edge, close to the charge points, if necessary.
- The backend service is greatly simplified - it's standard WSGI Django which is simpler to develop and test than ASGI and channels.
RabbitMQ is used as the RPC layer connecting be and ws. When a ws service starts up, it creates an ephemeral reply channel. Multiple ws services can be deployed independently. Each ws service communicates with the be server, which implements the OCPP protocol and contains all business logic.
Levity is currently running in production, managing a set of 20 Grizzl-E Smart charge points in a cohousing complex, to track individual electricity usage.
Levity was originally built for a specific EVSE management use case: each charge point has a single owner/user and no authentication is required. Authentication and user management are on the long term roadmap, however.
- Partial OCPP 1.6j support - Levity does not strive to be a complete reference implementation of OCPP, we are focused on solving practical EVSE management needs.
- Charge points, transactions, meter values, and OCPP messages are modeled and persisted by the system.
- Deployment via Docker Compose
- Out-of-the-box support for TLS via LetsEncrypt
- Middleware-style approach to extensibility (documentation TODO, see auto_remote_start.py for an example)
- Tested with the Grizzl-E Smart charger
- Satisfies the OCPP 1.6j requirement for synchronicity - each charge point is allocated a separate "call queue" which ensures that Levity will wait for a response to a CALL message before sending another CALL message.
The Grizzl-E Smart charger has several bugs in the current firmware (0.5) which are worked around by Levity:
- Websocket ping/pong is disabled, due to Grizzl-E sometimes sending invalid pongs, causing disconnection.
- The OCPP websocket protocol header is not required, because Grizzl-E does not send one.
- The RemoteStartTransaction message is delayed by 1 second after seeing the Preparing status, due to an intermittent race condition observed with Grizzl-E, where the Grizzl-E fails to transition to the
Charging
state if it receives the RemoteStartTransaction message too quickly.
- REST API for management, and to send OCPP commands on demand
- Reporting dashboard showing usage and other metrics
- Prometheus integration, to track application metrics and charge point fault rates
- Better handling of charge point faults, like a reset that interrupts a transaction in progress.
- Support for authentication, individual user accounts, user management, end-user portal, mobile app support.
- Packaging as a Django module, which could be added to other Django projects.