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:
- This tool should help you in learning and understanding the Open Charge Point Protocol (OCPP), therefore it doesn't hide any details, but make them easier to grasp
 - It's an all ECMAScript based project
 - NodeJs based server with server-side TypeScript support
 - Vue + Vuex + bulma + axios + vue-axios based HTML client with super simple requirejs no-transpiler setup
 - TODO: OCPP code should run inside a browser too
 
This software is licensed under Apache License, Version 2.0. See LICENSE.
Make sure you've npm installed.
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
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.
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.
npm run docs
Open the docs in ./public/docs or access them via ./start.sh and http://localhost:3000/docs
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
Run:
./start.sh --v1 --d
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"});
    }
});
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());
- 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)
 
- Cancel Reservation
 - Clear Cache
 - Clear Charging Profile
 - Data Transfer
 - Get Composite Schedule
 - Get Local List Version
 - Remote Stop Transaction
 - Reserve Now
 - Send Local List
 - Set Charging Profile
 - Unlock Connector
 
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;
