npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

em-ocpp-chargepoint-simulator

v0.0.4

Published

Fully scriptable OCCP 1.6 JSON chargepoint simulator

Downloads

19

Readme

Scriptable OCPP Chargepoint Simulator

Node.js CI

Intro

This simulator supports:

  • OCPP 1.6 (and security) with JSON
  • REST API with HTML Frontend
  • File based batch mode
  • Fully scriptable in JavaScript
  • ftp and csr operations
  • ws:// and wss:// (with client certificates)

Key development considerations:

license

This software is licensed under Apache License, Version 2.0. See LICENSE.

install & build

Make sure you've npm installed.

batch operation

Put the logic using JavaScript into a file (e.g. custom.js). Your file needs to export an async function with one parameter. This parameter will pass the connect(url: string): Chargepoint function to obtain a Chargepoint class object.

Example:

let cp;
try {
  // WebSocket Connect (no OCPP)
  cp = await connect('ws://localhost:8100/xyz');
  // typical startup OCPP
  await cp.sendBootnotification({chargePointVendor: "vendor", chargePointModel: "1"});
  await cp.sendHeartbeat();
  await cp.sendStatusNotification({connectorId: 0, errorCode: "NoError", status: "Available"});
  await cp.sendStatusNotification({connectorId: 1, errorCode: "NoError", status: "Available"});
  // register code for GetDiagnostics, UpdateFirmware, Reset, ...
  cp.answerGetDiagnostics( async (request) => {
    const fileName = "foo." + new Date().toISOString() + ".txt";
    cp.sendResponse(request.uniqueId, {fileName});
    await cp.sendDiagnosticsStatusNotification({status: "Idle"});
    await cp.sleep(5000);
    await cp.sendDiagnosticsStatusNotification({status: "Uploading"});
    await cp.ftpUploadDummyFile(request.payload.location, fileName);
    await cp.sendDiagnosticsStatusNotification({status: "Uploaded"});
  });
  // Typical charging session
  await cp.sendAuthorize({idTag: "ccc"});
  await cp.sendStatusNotification({connectorId: 1, errorCode: "NoError", status: "Preparing"});
  cp.transaction = await cp.startTransaction({connectorId: 1, idTag: "ccc", meterStart: 1377, timestamp: "2020-06-11T10:50:58.333Z"});
  await cp.sendStatusNotification({connectorId: 1, errorCode: "NoError", status: "Charging"});
  await cp.meterValues({connectorId: 1, transactionId: cp.transaction.transactionId, meterValue: [{ timestamp: "2020-06-11T10:50:58.765Z", sampledValue: [{value: 1387}] }]});
  await cp.stopTransaction({transactionId: cp.transaction.transactionId, meterStop: 1399, timestamp: "2020-06-11T10:50:59.148Z"});
  await cp.sendStatusNotification({connectorId: 1, errorCode: "NoError", status: "Finishing"});
  await cp.sendStatusNotification({connectorId: 1, errorCode: "NoError", status: "Available"});
} catch (err) {
  console.log(err);
} finally {
  cp.close();
}

Start it:

./start.sh --v ./custom.js
# or
cat custom.js | ./start.sh --v --stdin

Remote manipulation for the batch mode

You can start a web server within the batch operation script to allow the manipulation or observation of the script.

const webserver = cp.startListener(8080, '0.0.0.0', {'admin': 'secret'});
webserver.get('/stop', (req, res) => {
    res.send('stopped.');
    webserver.terminate();
});
webserver.get('/availability', (req, res) => {
    // this assumes we're storing the current availability in a variable called 'availability'
    res.send(availability);
});

This example starts a web server on 0.0.0.0:8080 using basic authentication. A user 'admin' with the password 'secret' allows under /stop to stop the webserver and under /availability to retrieve the charge points availability.

server operation

Default port for HTML is 3000. Change via env variable PORT. The WebSocket based Server to Client communication is using PORT+1.

./start.sh --v

Open http://localhost:3000/?connectTemplate=$connectUrl&cp=$chargePointName where chargePointName defines the ID of your chargepoint and connectUrl the connect string without the chargepoint-id at the end.

Example: http://localhost:3000/?connectTemplate=ws://foobar:8088/charging&cp=chargepoint001.

Create API docs

npm run docs

Open the docs in ./public/docs or access them via ./start.sh and http://localhost:3000/docs

TLS options

To pass a root CA file (to verify the server's certificate) use the --ca parameter from start.sh or set the env variable SSL_CERT_FILE.

To set client certificates (for mTLS) for a charge point with the id my-chargepoint-id use the following parameters:

./start.sh --v1 --keyStore '[{"id": "my-chargepoint-id", "key": "private.pem", "cert": "cert.pem"}]' --ca ./ca.pem

server operation - DEV mode

Run:

./start.sh --v1 --d

OCPP Operations

Trigger Message

The simulator will respond to all Trigger Message with status=NotImplemented if no answerTriggerMessage have been registered for this requestedMessage.

OCPP 1.6 defines those requestedMessage:

  • "BootNotification"
  • "DiagnosticsStatusNotification"
  • "FirmwareStatusNotification"
  • "Heartbeat"
  • "MeterValues"
  • "StatusNotification"

Example for BootNotification

cp.answerTriggerMessage("BootNotification", async (request) => {
    cp.sendResponse(request.uniqueId, {status: "Accepted"});
    await cp.sendBootnotification({chargePointVendor: "vendor", chargePointModel: "1"});
});

Another example for DiagnosticsStatusNotification

// your code for handling GetDiagnostics will need to update a variable
// currentDiagnosticsStatus with the current state
cp.answerTriggerMessage("DiagnosticsStatusNotification", async (request) => {
    if(currentDiagnosticsStatus) {
        cp.sendResponse(request.uniqueId, {status: "Accepted"});
        await cp.sendDiagnosticsStatusNotification({status: currentDiagnosticsStatus});
    } else {
        cp.sendResponse(request.uniqueId, {status: "Rejected"});
    }
});

ExtendedTriggerMessage

OCPP 1.6 security defines those requestedMessage:

  • "BootNotification"
  • "LogStatusNotification"
  • "FirmwareStatusNotification"
  • "Heartbeat"
  • "MeterValues"
  • "SignChargePointCertificate"
  • "StatusNotification"

Example of SignChargePointCertificate:

let tmpKey;
cp.answerExtendedTriggerMessage("SignChargePointCertificate", async (request) => {
    cp.sendResponse(request.uniqueId, {status: "Accepted"});
    const {key, csr} = await cp.generateCsr('/OU=Ocpp-Simulator/O=Ocpp Simu Inc./L=Paradise City/ST=The State/C=US/CN=the-best-chargepoint');
    tmpKey = key;
    await cp.sendSignCertificate({csr, "typeOfCertificate": "ChargingStationCertificate"});
});
cp.answerCertificateSigned( async (request) => {
    if(!tmpKey) {
        cp.sendResponse(request.uniqueId, {status: "Rejected"});
        return;
    }
    cp.sendResponse(request.uniqueId, {status: "Accepted"});
    const keystore = cp.keystore();
    // this will overwrite the current key/cert files
    const filenames = keystore.save(false, tmpKey, request.payload.cert.join('\n'));
    // alternatively certs/key can be written in a new file
    // keystore.save('-' + new Date().toISOString(), tmpKey, request.payload.cert.join('\n'));
    await cp.reConnect();
    await cp.sendBootnotification({chargePointVendor: "vendor", chargePointModel: "1"});
}, cp.CERTIFICATE_SIGNED_OPTIONS_PEM_ENCODER());

Supported

  • BootNotification
  • HeartBeat
  • StatusNotification
  • Authorize
  • StartTransaction
  • StopTransaction
  • MeterValues
  • Get Diagnostics
  • Diagnostics Status Notification
  • Update Firmware
  • Firmware Status Notification
  • Trigger Message
  • Reset
  • Get Configuration
  • Change Configuration
  • Change Availability
  • Remote Start Transaction
  • ExtendedTriggerMessage (1.6 security)
  • SignCertificate (1.6 security)
  • CertificateSigned (1.6 security)
  • Clear Charging Profile

Not supported (yet)

  • Cancel Reservation
  • Clear Cache
  • Data Transfer
  • Get Composite Schedule
  • Get Local List Version
  • Remote Stop Transaction
  • Reserve Now
  • Send Local List
  • Set Charging Profile
  • Unlock Connector

Architecture

alt text

Disable host name verification

To disable the host name verification when using TLS, apply this patch:

diff --git a/src/websocket-connection-centralsystem.ts b/src/websocket-connection-centralsystem.ts
index ec53fdc..e0323a8 100644
--- a/src/websocket-connection-centralsystem.ts
+++ b/src/websocket-connection-centralsystem.ts
@@ -32,6 +32,7 @@ export class WSConCentralSystem{
           options.key = fs.readFileSync(keyStoreElement.key);
           options.cert = fs.readFileSync(keyStoreElement.cert);
         }
+        options.checkServerIdentity = () => undefined;
       }
       this.ws = new WebSocket(this.url, "ocpp1.6", options);
       let promiseResolved = false;

Publishing

  1. Bump version number in package.json
  2. Run npm tsc
  3. Run npm publish