Skip to content

aws-solutions-library-samples/guidance-for-modernizing-electric-vehicle-charging-on-aws

Building an OCPP-Compliant electric vehicle charge point operator solution using AWS IoT Core

Table of content

Overview

Most commercially available CPs implement OCPP as a means of bi-directional publish-and-subscribe communication with a CPO. Operating a CPO on AWS requires the introduction of an OCPP WebSocket endpoint, with which CPs communicate. That endpoint, described here as the OCPP Gateway, acts as a proxy between OCPP and MQTT, enabling integration with AWS IoT Core and downstream CPO services built on AWS.

This Guidance demonstrates how to build and modernize your electric vehicle (EV) charging station using AWS IoT Core. It highlights a modern Charging Station Management System (CSMS) built with serverless technologies, offering improved performance, lower costs, enhanced security, and compliance with the Open Charge Point Protocol (OCPP) for charger-to-cloud interoperability. The IoT Core and Lambda integration layer acts as a abstraction and translation service, enabling diverse charging stations to communicate with the CSMS seamlessly. This solves the interoperability challenge of integrating proprietary protocols and legacy systems, allowing the CSMS to work with any compliant charging station. By using this loosely coupled, event-driven architecture, the solution can scale to support a large network of charging stations while maintaining a consistent interface for the consuming applications. The Guidance helps establish a reliable, secure, highly available EV charging platform with 99.9% uptime, as required by law in certain countries. Designed for reduced operating expenses and supporting OCPP, the serverless CSMS helps ensure a seamless charging experience while meeting regulatory requirements.

This solution demonstrates how you can use AWS to build a scalable CPO by deploying the OCPP Gateway to integrate with AWS IoT Core. The steps below will walk you through the deployment of an OCPP Gateway into your AWS account, will demonstrate how you can simulate CP message, and will provide examples of you how can act on those message using AWS resources.

For more information read the related blog post.

Architecture

The architecture diagram below depicts the resources that this solution will deploy into your account.

Figure 1: OCPP Gateway solution stack architecture
Figure 1: OCPP Gateway solution stack architecture

The OCPP Gateway is deployed as an Amazon ECS application which can run on either AWS Fargate or Amazon Elastic Compute Cloud (EC2). AWS Fargate eliminates the need for infrastructure management and is the preferred option for this solution. Containerized applications can be scaled horizontally, allowing the OCPP Gateway to automatically scale up or down as the number of connected CPs changes. The long running nature of ECS tasks allows for WebSockets connections to be maintained for extended periods, reducing network traffic and connection overheads.

A Network Load Balancer (NLB) fronts multiple OCPP Gateway containers. The NLB provides a single, fully qualified domain name (FQDN) that serves as the OCPP endpoint to which CPs initiate connection. Upon connection initiation, the NLB will route the charge point connection to one of the OCPP Gateway instances, which will establish the WebSocket connection between itself and the CP.

When a CP establishes a socket connection with an instance of the OCPP Gateway, that Handler sets up an MQTT connection to AWS IoT Core using the CP’s unique identifier as the Thing ID. That client subscribes to MQTT message topics associated with that CP.

The MQTT client implemented by the OCPP Gateway is socket aware, thereby providing a one-to-one association between the MQTT subscription and the CP. Any messages initiated by the CPO will be delivered to the MQTT client associated with the destination CP and forwarded over the socket to that CP. AWS IoT Core is highly elastic and will readily scale as more CPs are on-boarded.

Prerequisites

Verify that your environment satisfies the following prerequisites:

You have:

  1. An AWS account
  2. AdministratorAccess policy granted to your AWS account (for production, we recommend restricting access as needed)
  3. Both console and programmatic access
  4. AWS CLI installed and configured to use with your AWS account
  5. NodeJS 12+ installed
  6. Typescript 3.8+ installed
  7. AWS CDK CLI installed
  8. Docker installed
  9. Python 3+ installed

Deployment

Prepare the CDK

The solution will be deployed into your AWS account using infrastructure-as-code wih the AWS Cloud Development Kit (CDK).

  1. Clone the repository:
git clone https://github.com/aws-samples/aws-ocpp-gateway
  1. Navigate to this project on your computer using your terminal:
cd aws-ocpp-gateway
  1. Install the project dependencies by running this command:
npm install
  1. Set environment variables for CDK to the target AWS account ID and region where you wish to deploy this stack

Note: AWS IoT Core is available in these AWS regions.

export CDK_DEPLOY_ACCOUNT=targetAccountId (e.g. 12345678910)
export CDK_DEPLOY_REGION=targetRegion (e.g. eu-west-1)
  1. (Optional) Bootstrap AWS CDK on the target account and regioon

Note: This is required if you have never used AWS CDK before on this account and region combination. (More information on CDK bootstrapping).

npx cdk bootstrap aws://{targetAccountId}/{targetRegion}

(Optional) Enable WebSockets using TLS with your own domain name

If you have an Amazon Route 53 hosted zone in your account, this solution can automatically:

  • Create subdomain (A Record) gateway.yourdomain.com
  • Create an AWS Certificate Manager (ACM) SSL certificate for it
  • Enable TLS for your gateway wss://gateway.yourdomain.com
  • Uncomment this line in /bin/aws-ocpp-gateway.ts and replace yourdomain.com with your own domain name (i.e. example.com)
  // domainName: 'yourdomain.com',

Deploy the solution to your AWS Account

  1. Verify that Docker is running with the following command:
docker version

Note: If you get an error like the one below, then Docker is not running and need to be restarted:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
  1. Deploy the OCPP Gateway using the following CDK command:
npx cdk deploy

Note: This step can take about 10 minutes, depending on your computer and network speed.

  1. You can view the progress of your CDK deployment in the CloudFormation console in the selected region.
Screenshot: CloudFormation stack resources
Screenshot: AWS CloudFormation stack resources
  1. Once deployed, take note of the AwsOcppGatewayStack.websocketURL value

Note: This WebSocket URL is the entry point that will be set in your CP configurations or in the EV Charge Point simulator described below.

If you used your own domain, your output will look like:

...
Outputs:
AwsOcppGatewayStack.loadBalancerDnsName = gateway.example.com
👉 AwsOcppGatewayStack.websocketURL = wss://gateway.example.com
...

Otherwise, like this:

...
Outputs:
AwsOcppGatewayStack.loadBalancerDnsName = ocpp-gateway-xxxxxxx.elb.xx-xxxx-x.amazonaws.com
👉 AwsOcppGatewayStack.websocketURL = ws://ocpp-gateway-xxxxxxx.elb.xx-xxxx-x.amazonaws.com
...

Troubleshooting

Docker not running

If you get an error like the one below, then Docker is not running and need to be restarted:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Verify that Docker is running with the following command:

docker version

If not, please start your docker daemon

Docker not logged in

If you get an error about your docker not being logged in chances are you've previously authenticated to Amazon ECR Public and your token has expired.

To resolve this error it may be necessary to run the following command

docker logout public.ecr.aws

the try to deploy again.

This will result in an unauthenticated pull and should resolve the error.

Docker image platform mismatch

If you get an error like the one below:

...
 ---> [Warning] The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested
...

You could try to switch the default chip architecure for ECS from ARM to X86_64 by uncommenting this line in bin/aws-ocpp-gateway.ts

Docker: failed to bundle asset

If you get an error like the one below:

...
Failed to bundle asset [...], bundle output is located [...]: Error: docker exited with status 1
...

chances are your docker install is using a WSL2 based engine, switching to Hyper-V should solve the issue.

Simulating CP connectivity

We have provided the simulate.py Python script to help you test and explore the capability of the OCPP Gateway and AWS IoT Core without the need for a physical CP. Other OCPP simulators, like OCPP-2.0-CP-Simulator, can also be used.

Simulation setup

  1. In AWS Explorer, select your region and open AWS IoT Core, All devices, Things. On the Things tab choose Create a things.

  2. Select Create single thing and choose Next

  3. Enter a Thing name

Note: Each EV Charge Point must map to a single IoT Thing. For our test, we'll set the Thing name as CP1

Screenshot: Creating an IoT Thing
Screenshot: Creating an IoT Thing
  1. Choose Next

  2. For Device certificate, select Skip creating a certificate at this time, and choose Create thing

Screenshot: Skip the certification creation
Screenshot: Skip the certification creation
  1. Navigate to this folder with your terminal:
cd ev-charge-point-simulator
  1. Create a Python virtual environment and activate it by running this command:
python3 -m venv venv && source venv/bin/activate
  1. Install the Python dependencies by running:
pip3 install -r requirements.txt

Simulate an EV charge point boot and heartbeat notification

The Python script simulates some basic functionality of an EV charge point:

  • Sending a BootNotification, including attributes about the CP hardware
  • Sending Heartbeat messages based on a frequency instructed by the CPO (this is defined by the interval parameter returned in the response to the BootNotification)
  1. Run the Python script using the following command, making sure to replace the --url value with the AwsOcppGatewayStack.websocketURL returned from the cdk deployment:
python3 simulate.py --url {websocket URL generated from the AWS OCPP Stack} --cp-id CP1 

Note: we are using --cp-id CP1 which must match the value of the IoT Thing created above. If the --cp-id doesn't match the IoT Thing name, the connection will be rejected by the OCPP Gateway.

A successful output should look like this:

(venv) ev-charge-point-simulator % python3 simulate.py --url {websocket URL generated from the AWS OCPP Stack} --cp-id CP1 
INFO:ocpp:CP1: send [2,"0678cb2a-a7a2-42bc-8037-d01164e77ac6","BootNotification",{"chargingStation":{"model":"ABC 123 XYZ","vendorName":"Acme Electrical Systems","firmwareVersion":"10.9.8.ABC","serialNumber":"CP1234567890A01","modem":{"iccid":"891004234814455936F","imsi":"310410123456789"}},"reason":"PowerUp"}]
INFO:ocpp:CP1: receive message [3,"0678cb2a-a7a2-42bc-8037-d01164e77ac6",{"currentTime":"2023-02-16T19:00:18.630818","interval":10,"status":"Accepted"}]
INFO:root:CP1: connected to central system
INFO:root:CP1: heartbeat interval set to 10
INFO:ocpp:CP1: send [2,"9b7933a7-5216-496d-9bb0-dae45014bb98","Heartbeat",{}]
INFO:ocpp:CP1: receive message [3,"9b7933a7-5216-496d-9bb0-dae45014bb98",{"currentTime":"2023-02-16T19:00:19.073675"}]

This exchange represents a successful simulation of a CP, first sending a BootNotification, followed by subsequent Heartbeat at the specified interval. The output includes both the simulated OCPP message sent from the CP to AWS IoT (prefixed send) and the response received from AWS (prefixed received message).

  1. To simulate with a different CP, set a different value for the --cp-id argument.

Note: if the --cp-id value doesn't have a correspondent IoT Thing the OCPP Gateway will reject the connection. Here is an unsuccessful example passing --cp-id CP2, which is not registered as a Thing in IoT:

(venv) ev-charge-point-simulator % python3 simulate.py --url {websocket URL generated from the AWS OCPP Stack} --cp-id CP2 
INFO:ocpp:CP2: send [2,"32dc5b6e-77b0-4105-b217-28e20b579ecc","BootNotification",{"chargingStation":{"model":"ABC 123 XYZ","vendorName":"Acme Electrical Systems","firmwareVersion":"10.9.8.ABC","serialNumber":"CP1234567890A01","modem":{"iccid":"891004234814455936F","imsi":"310410123456789"}},"reason":"PowerUp"}]
ERROR:root:CP2: received 1008 (policy violation) Charge Point CP2 not registered as an IoT Thing; then sent 1008 (policy violation) Charge Point CP2 not registered as an IoT Thing

Monitor OCPP activity in the AWS Console

Messages from and to the CP are brokered through AWS IoT Core. These messages utilize the MQTT publish-and-subscribe protocol. You can see these messages in the console.

  1. In AWS Explorer, select your region and open AWS IoT Core, MQTT test client

  2. In the test client, select the Subscribe to a topic tab, and subscribe to these two topics by entering these values in the Topic filter:

    a. To view all messages from CP to AWS

+/in

b. To view all messages from AWS to CP

+/out
Screenshot: Subscribe to Topics
Screenshot: Subscribe to Topics
  1. Run the Python script to simulate a CP and watch the messages in the MQTT test client

Track EV Charge Point hardware attributes in device shadows

When a CP sends a BootNotification, its hardware attributes are stored in a Device Shadow associated with the IoT Thing. You can see these attributes in the console.

  1. In AWS Explorer, select your region and open AWS IoT Core, All devices, Things

  2. Toggle the check box against the Thing created previously

  3. Select the Device Shadows tab.

  4. Select the Classic Shadow device shadow name hyperlink to see the Device Shadow document and the hardware attributes reported by the EV Charge Point:

{
  "state": {
    "reported": {
      "chargingStation": {
        "model": "ABC 123 XYZ",
        "vendorName": "Acme Electrical Systems",
        "firmwareVersion": "10.9.8.ABC",
        "serialNumber": "CP1234567890A01",
        "modem": {
          "iccid": "891004234814455936F",
          "imsi": "310410123456789"
        }
      },
      "reason": "PowerUp"
    }
  }
}
Screenshot: IoT Thing shadow document
Screenshot: IoT Thing shadow document
  1. Simulate different CP hardware attributes by passing these arguments into the simulate.py script and verify their affect on the Device Shadow:
  • --cp-serial - to set the serial number
  • --cp-model - to set the model identification
  • --cp-version - to set the firmware version
  • --cp-vendor - to set the vendor name

(Optional) More things to try yourself

This section provides some suggested simulations and tests you can try yourself to better appreciate the art of the possible as it relates to building an OCPP-compliant CPO on AWS.

Load testing

AWS IoT Core is a fully managed, highly elastic service that scales to support millions of Things. The OCPP Gateway uses auto-scaling to automatically scale-up as your fleet of CPs grows.

  1. Using a load testing tool or the included Apache JMeter configuration, simulate a load of thousands of CPs

  2. In AWS Explorer, select your region and open Elastic Container Service

  3. Under Clusters open the hyperlink of the cluster created by the OCPP Gateway stack (will be prefixed AwsOcppGatewayStack)

  4. Select the Metrics tab

  5. Watch how the number of tasks increases from one to two, etc. as your load increases.

Auto-scaling is configured to trigger when the average CPU utilization exceeds 60% -- you can drive more load or decrease this threshold to test the affect.

If you are using JMeter or similar load tester be cautious of the number of threads (Things) you create and duration you run your test for. The solution will readily scale to many thousands of Things and will run for indefinite periods of time, which may result in unexpected charges in your AWS account. We suggest using the load test to test scalability, but to halt the test quickly to reduce costs.

Rules for AWS IoT

Rules for AWS IoT can be used to filter MQTT messages and route them to other services in AWS. Create a new rule to capture Heartbeat messages and record them in a DynamoDB table for a last known event.

  1. In AWS Explorer, select your region and open DynamoDB

  2. Select Create table

  3. Provide the Table name chargePointHeartbeat and set the Partition key to chargePointId

  4. Choose Create table

  5. In AWS Explorer, select your region and open AWS IoT Core, Message routing, Rules

  6. Select Create Rule

  7. Provide the Rule name chargePointHeartbeat and choose Next

  8. Enter the following into the SQL statement and choose Next

SELECT 
  topic(1) AS chargePointId,
  timestamp() AS lastTimestamp
FROM '+/in'
WHERE get(*, 2) = 'Heartbeat'
  1. For Action 1, choose DynamoDBv2
Screenshot: IoT Rule SQL statement
Screenshot: IoT Rule SQL statement
  1. Select the Amazon DynamoDB table created above for Table name
Screenshot: IoT Rule action
Screenshot: IoT Rule action
  1. Select Create new role, provide the Role name chargePointHeartbeat, choose Create

  2. Choose Next and Create

  3. Navigate back to DynamoBD and select Tables, Explore Items

  4. For Tables, choose the DynamoDB table created previously

  5. Run the Python script to simulate a CP and watch as heartbeat are added and update in the DynamoDB table

Connection handling

A single CP should only maintain one connection to one OCPP Gateway, otherwise routing of responses from the CPO to the right connection may be affected. You can simulate a reconnection attempt.

  1. (Optional) If you don't already have it, download and install the wscat utility

  2. Open a terminal windows and establish a WebSocket connection:

wscat -c {AwsOcppGatewayStack.websocketURL}/CP1 -s ocpp2.0.1
Connected (press CTRL+C to quit)
>
  1. In a second terminal window run the same command, attempting to create another connection using the same CP, e.g. CP1

  2. Once this new connection is established you'll see that the prior connection is automatically closed:

Disconnected (code: 1000, reason: "")
  1. Testing a connection with a CP that is not configured as an IoT Thing will result in the connection attempt being rejected:
wscat -c {AwsOcppGatewayStack.websocketURL}/CPX -s ocpp2.0.1
Connected (press CTRL+C to quit)
Disconnected (code: 1008, reason: "Charge Point CPX not registered as an IoT Thing")

Clean up

When you are done running simulations, deactivate the Python virtual environment (venv) by executing this command in your terminal:

deactivate

You can remove the OCPP Gateway Stack and all the associated resources created in your AWS account by running the following command:

npx cdk destroy

Cost

You are responsible for the cost of the AWS services used while running this solution guidance. As of August 2024, the cost for running this guidance with the default settings is $175 per month for 500 chargers. We recommend creating a budget through AWS Cost Explorer to help manage costs. Prices are subject to change. For full details, refer to the pricing webpage for each AWS service used in this Guidance.

Estimated monthly cost breakdown

The following table provides a sample cost breakdown for deploying this guidance in the us-east-2 region for one month operating 500 charges (NOTE the cost to scale this solution is not linear, doubling the number of chargers increases the cost by less than $100). The AWS cost calculator is available here. Please note that these cost calculations are based on the default configuration options of the guidance deployment method described below.

AWS service Dimensions Cost per month [USD]
Amazon Elastic Compute Cloud (EC2) NatGateway $ 55
IoT AWS IoT $ 30
Virtual Private Cloud (VPC) Public IPv4 Addresses $ 25
Elastic Load Balancing Network $ 20
CloudWatch Cloudwatch, PutLogEvents $ 20
Simple Queue Service (SQS) SQS $ 10
Data Transfer Bandwidth $ 7
Lambda ARM $ 4
Secrets Manager Secrets $ 2
Route 53 1 Hosted Zone $ 1
ECR TimedStorage $ 1
Total estimate $175

Credits

This sample was made possible thanks to the following libraries:

License

This library is licensed under the MIT-0 License. See the LICENSE file.