The Internet of Things on AWS – Official Blog

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

The shift from fossil fuels to electric powered vehicles is a key component of government and commercial commitments to achieve net-zero emissions by 2050. It is projected that the United States alone will require a national network of at least 500,000 electric vehicle (EV) chargers by 2030 to support the projected number of EVs on the road [1][2]. Globally, governments and industries are partnering to build millions of public charging and private fleet charging networks [3].

Installing and powering the physical charging infrastructure is just the first step — chargers (Charge Points or “CP”) need to be continuously monitored and managed by their operators (Charge Point Operators or “CPO”). CPOs are responsible for regular remote and on-site maintenance, collecting health metrics, and managing operational configurations. CPOs are also responsible for ensuring that the CPs are compatible with the latest industry standards and protocols, like Open Charge Point Protocol (OCPP) and ISO 15118. And all this must be implemented with security measures that can support CPs at scale.

This post demonstrates how AWS services like AWS IoT Core, Amazon Elastic Container Service (Amazon ECS), and AWS Lambda can be used to build a highly-scalable, low-latency electric vehicle charge point operator system based on the EV industry standard, OCPP.

About AWS IoT Core

AWS IoT Core lets you connect billions of devices and route trillions of messages to and from AWS services without managing infrastructure. AWS IoT Core handles the heavy-lifting of scaling and message routing—making it easier for customers needing to support large fleets of remote devices, like CPs, communicating through publish-and-subscribe patterns. AWS IoT Core natively implements MQTT, HTTPS, and MQTT over WebSockets, and can be adapted to support other protocols, like OCPP.

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.

The following architecture diagram illustrates the high-level end-to-end solution you will build in this blog post.

Figure 1: Charge Point OCPP message proxied to CPO Service via one-to-one relationship between WebSocket connection and MQTT topic
Figure 1: Charge Point OCPP message proxied to CPO Service via one-to-one relationship between WebSocket connection and MQTT topic

Architecture

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

Figure 2: OCPP Gateway solution stack architecture
Figure 2: 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.

Solution walk-through

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.

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

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.git
  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
...

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

Conclusion

In this post, you learned how AWS Services can be used to build a highly-scalable, low-latency CPO. Using AWS Fargate, you deployed the OCPP Gateway, an OCPP-to-MQTT proxy, which allowed you to take advantage of AWS IoT Core’s managed routing and scaling functionality to deploy and operate your Charge Point Operator solution on AWS. You learned how Rules for AWS IoT can be used to filter and route messages from the EV charge point to downstream AWS services like Amazon DynamoDB and AWS Lambda to create custom reporting and automated workflows.

The solution and the sample code have been made available as open-source and can be readily adapted to your specific business needs.

We hope you found this post informative and the walk-through helpful. As always, AWS welcomes feedback. Please feel free to connect/message the authors through their LinkedIn profiles include below.

(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

About the authors

Garry Galinsky

Garry Galinsky

Garry Galinsky is a Principal Solutions Architect supporting Amazon on AWS. LinkedIn

Bigad Soleiman

Bigad Soleiman

Bigad Soleiman is a Sr. Lead Prototyping Engineer on the AWS Prototyping Team. Leading largest and strategic AWS customers navigating complex core business problems from concept to production across multiple domains. LinkedIn

This post is a part of a broader effort to support the OCPP protocol on AWS. Special thanks to David Goehrig, Sergey Pugachev, Clement Rey, and Ozan Cihangir for their contributions to this effort.