import * as WebSocket from 'ws'; import {RemoteConsoleTransmissionType, WSConRemoteConsole} from "./remote-console-connection"; import {wsConCentralSystemRepository, wsConRemoteConsoleRepository} from "./state-service"; import {FtpSupport} from "./ftp"; import { AuthorizePayload, AuthorizeResponse, BootNotificationPayload, BootNotificationResponse, CertificateSignedPayload, ChangeAvailabilityPayload, ChangeConfigurationPayload, DataTransferPayload, DiagnosticsStatusNotificationPayload, ExtendedTriggerMessagePayload, FirmwareStatusNotificationPayload, GetConfigurationPayload, GetDiagnosticsPayload, MessageType, MeterValuesPayload, OcppRequest, OcppResponse, Payload, RemoteStartTransactionPayload, ResetPayload, SignCertificatePayload, StartTransactionPayload, StartTransactionResponse, StatusNotificationPayload, StopTransactionPayload, StopTransactionResponse, TriggerMessagePayload, UpdateFirmwarePayload, DeleteCertificatePayload, } from './ocpp1_6'; import {CertManagement, Csr} from "./cert-management"; import {KeyStore, KeyStoreElement} from "./keystore"; import * as http from "http"; import * as express from "express"; import {IRouter} from "express"; import {createHttpTerminator} from 'http-terminator'; import * as expressBasicAuth from "express-basic-auth"; import {QueueSubmitLayer} from "./queue-submit-layer"; import {log} from "./log"; import {Config} from "./config"; const LOG_NAME = 'ocpp-chargepoint-simulator:simulator:ChargepointOcpp16Json'; const LOG_NAME_OCPP = 'ocpp-chargepoint-simulator:simulator:ChargepointOcpp16Json:OCPP'; /** * Generates a UUID v4 - e.g. 550e8400-e29b-11d4-a716-446655440000 * Needed for the unique-id of a OCPP request */ function uuidv4(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c: string) { const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * Converts an object of type OcppResponse (TypeScript) into an OCPP response (protocol) array. * * @param resp an Array representing an OCPP Response as [message-type-id, unique-id, payload] */ function ocppResToArray<T>(resp: OcppResponse<T>): Array<string | number | Payload> { return [resp.messageTypeId, resp.uniqueId, resp.payload]; } /** * Defines options used when registering OCPP answers */ interface AnswerOptions<T> { requestConverter(resp: OcppRequest<T>): Promise<OcppRequest<T>>; } /** * Stores a function called when a OCPP requested asked to be answered by the simulator. Also stores options for this. */ interface OcppRequestWithOptions<T> { cb: (request: OcppRequest<T>) => void, options?: AnswerOptions<T> } /** * Implements an OCPP 1.6 JSON speaking Chargepoint. This is the main API for a Chargepoint. */ export class ChargepointOcpp16Json { private config: Config = new Config(); /** Reference to the class handling the WebSocket connection to the central system */ private wsConCentralSystem: QueueSubmitLayer = new QueueSubmitLayer(this, this.config); /** * OCPP requests started from the central system need to be answered by code from this call. * This map stores all ocpp message names (action) to the callback function (with options) implementing this OCPP message. * */ private registeredCallbacks: Map<string, OcppRequestWithOptions<Payload>> = new Map(); /** * Special cases: "TriggerMessage", to make the implementation easier, a user can register just the code for a type of * trigger-message and not the whole trigger message logic */ private registeredCallbacksTriggerMessage: Map<string, (OcppRequest) => void> = new Map(); private registeredCallbacksExtendedTriggerMessage: Map<string, (OcppRequest) => void> = new Map(); /** holds a callback for onClose, may be null */ onCloseCb: () => void; constructor() { this.buildTriggerMessage(); this.buildExtendedTriggerMessage(); } /** * If connected to the CS, outputs a string or object to the remote console. If not remote console is connected, sends the output into the * logging backend. * * @param output string or object send to the log output console * @return true if log was successfully sent to remote-console, false if logged locally */ log(output: (string | object)): boolean { if (this.areAnyRemoteConsolesConnected()) { this.sendMsgRemoteConsole(RemoteConsoleTransmissionType.WS_ERROR, output); return true; } else { log.debug(LOG_NAME, this.config.cpName ? this.config.cpName : "-", output); return false; } } /** * Waits the time "millies" until the Promise resolves. Will never reject. * * @param millis time to sleep in milli seconds */ sleep(millis: number): Promise<void> { return new Promise<void>((resolve) => { setTimeout(resolve, millis); }) } /** * Connects the WebSocket to the central system. No OCPP happening. DO NOT CALL THIS METHOD DIRECTLY. * * @param url to connect to. Must start with ws:// or ws:// * @returns a Promise which resolves when the connection is established and rejects when the connection cannot be established. */ connect(url: string, cpName?: string, keyStoreElement?: KeyStoreElement): Promise<void> { log.debug(LOG_NAME, cpName, 'connect as ' + cpName + " with " + JSON.stringify(keyStoreElement)); this.config.init(url, cpName, keyStoreElement); return this.wsConCentralSystem.connect(); } /** * Re-connects the WebSocket to the central system. No OCPP happening. */ reConnect(): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'reConnect'); this.wsConCentralSystem.close(); return new Promise<void>((resolve, reject) => { // wait before re-connect, as it takes a couple of millies until onClose() in queue-submit-layer is called setTimeout(async () => { try { await this.wsConCentralSystem.connect(); resolve(); } catch(err) { reject(err); } }, 1000); }) } /** * Sends a OCPP heartbeat message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. */ sendHeartbeat(): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendHeartbeat'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'Heartbeat', payload: {} }); } /** * Sends a OCPP boot notification message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload boot notification payload object */ sendBootnotification(payload: BootNotificationPayload): Promise<BootNotificationResponse> { log.debug(LOG_NAME, this.config.cpName, 'sendBootnotification'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'BootNotification', payload }); } /** * Sends a OCPP status notification message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload status notification payload object */ sendStatusNotification(payload: StatusNotificationPayload): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendStatusNotification'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'StatusNotification', payload }); } /** * Sends a OCPP authorize message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload authorize payload object */ sendAuthorize(payload: AuthorizePayload): Promise<AuthorizeResponse> { log.debug(LOG_NAME, this.config.cpName, 'sendAuthorize'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'Authorize', payload }); } /** * Sends a OCPP start transaction message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload start transaction payload object */ startTransaction(payload: StartTransactionPayload): Promise<StartTransactionResponse> { log.debug(LOG_NAME, this.config.cpName, 'sendStartTransaction'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'StartTransaction', payload }); } /** * Sends a OCPP stop transaction message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload stop transaction payload object */ stopTransaction(payload: StopTransactionPayload): Promise<StopTransactionResponse> { log.debug(LOG_NAME, this.config.cpName, 'sendStopTransaction'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'StopTransaction', payload }); } /** * Sends a OCPP meter values message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload meter values payload object */ meterValues(payload: MeterValuesPayload): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendMeterValues'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'MeterValues', payload }); } /** * Sends a OCPP data transfer message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload data transfer payload object */ sendDataTransfer(payload: DataTransferPayload): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendDataTransfer'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'DataTransfer', payload }); } /** * Registers a function to implement logic for OCPP's Get Diagnostics message. The function provided must at least call * cp.sendResponse(request.uniqueId, {fileName}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<GetDiagnosticsPayload>) => void */ answerGetDiagnostics<T>(cb: (request: OcppRequest<GetDiagnosticsPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerGetDiagnostics'); this.registeredCallbacks.set("GetDiagnostics", {cb, options}); } /** * Registers a function to implement logic for OCPP's Update Firmware message. The function provided must at least call * cp.sendResponse(request.uniqueId, {}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<UpdateFirmwarePayload>) => void */ answerUpdateFirmware<T>(cb: (request: OcppRequest<UpdateFirmwarePayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerUpdateFirmware'); this.registeredCallbacks.set("UpdateFirmware", {cb, options}); } /** * Registers a function to implement logic for OCPP's Reset message. The function provided must at least call * cp.sendResponse(request.uniqueId, {status}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<ResetPayload>) => void */ answerReset<T>(cb: (request: OcppRequest<ResetPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerReset'); this.registeredCallbacks.set("Reset", {cb, options}); } /** * Registers a function to implement logic for OCPP's Get Configuration message. The function provided must at least call * cp.sendResponse(request.uniqueId, {...}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<GetConfigurationPayload>) => void */ answerGetConfiguration<T>(cb: (request: OcppRequest<GetConfigurationPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerGetConfiguration'); this.registeredCallbacks.set("GetConfiguration", {cb, options}); } /** * Registers a function to implement logic for OCPP's Change Configuration message. The function provided must at least call * cp.sendResponse(request.uniqueId, {...}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<ChangeConfigurationPayload>) => void */ answerChangeConfiguration<T>(cb: (request: OcppRequest<ChangeConfigurationPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerChangeConfiguration'); this.registeredCallbacks.set("ChangeConfiguration", {cb, options}); } /** * Registers a function to implement logic for OCPP's Change Availability message. The function provided must at least call * cp.sendResponse(request.uniqueId, {...}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<ChangeAvailabilityPayload>) => void */ answerChangeAvailability<T>(cb: (request: OcppRequest<ChangeAvailabilityPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerChangeAvailability'); this.registeredCallbacks.set("ChangeAvailability", {cb, options}); } /** * Registers a function to implement logic for OCPP's 1.6 secured Certificate Signed message. The function provided must at least call * cp.sendResponse(request.uniqueId, {...}); to send the a OCPP CALLRESULT message. * * @param cb callback with signature (request: OcppRequest<CertificateSignedPayload>) => void */ answerCertificateSigned<T>(cb: (request: OcppRequest<CertificateSignedPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerCertificateSigned'); this.registeredCallbacks.set("CertificateSigned", {cb, options}); } /** * Registers a function to implement logic for start a remote transaction. The function provided must at least call * cp.sendResponse(request.uniqueID, {...}); to send the OCPP CALLRESULT message. * * @param cb cb callback with signature (request: OcppRequest<CertificateSignedPayload>) => void * @param options */ answerRemoteStartTransaction<T>(cb: (request: OcppRequest<RemoteStartTransactionPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerRemoteStartTransaction'); this.registeredCallbacks.set("RemoteStartTransaction", {cb, options}); } /** * Registers a function to implement logic for stop a remote transaction. The function provided must at least call * cp.sendResponse(request.uniqueID, {...}); to send the OCPP CALLRESULT message. * * @param cb cb callback with signature (request: OcppRequest<CertificateSignedPayload>) => void * @param options */ answerRemoteStopTransaction<T>(cb: (request: OcppRequest<RemoteStartTransactionPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerRemoteStopTransaction'); this.registeredCallbacks.set("RemoteStopTransaction", {cb, options}); } /** * Registers a function to implement logic for all the data tranfer operations in an lms * * @param cb * @param options */ answerDataTransfer<T>(cb: (request: OcppRequest<DataTransferPayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerDataTransfer'); this.registeredCallbacks.set("DataTransfer", {cb, options}); } /** * Registers a function to implement logic for OCPP's Trigger Message message. The function provided must at least call * cp.sendResponse(request.uniqueId, {...}); to send the a OCPP CALLRESULT message. * * @param requestedMessage name of trigger message * @param cb callback with signature (request: OcppRequest<TriggerMessagePayload>) => void */ answerTriggerMessage<T>(requestedMessage: string, cb: (request: OcppRequest<TriggerMessagePayload>) => void): void { log.debug(LOG_NAME, this.config.cpName, 'answerTriggerMessage'); this.registeredCallbacksTriggerMessage.set(requestedMessage, cb); this.buildTriggerMessage(); } /** * Registers a function to implement logic for OCPP's 1.6 secured Extended Trigger Message message. The function provided must at least call * cp.sendResponse(request.uniqueId, {...}); to send the a OCPP CALLRESULT message. * * @param requestedMessage name of trigger message * @param cb callback with signature (request: OcppRequest<TriggerMessagePayload>) => void */ answerExtendedTriggerMessage<T>(requestedMessage: string, cb: (request: OcppRequest<ExtendedTriggerMessagePayload>) => void): void { log.debug(LOG_NAME, this.config.cpName, 'answerExtendedTriggerMessage'); this.registeredCallbacksExtendedTriggerMessage.set(requestedMessage, cb); this.buildExtendedTriggerMessage(); } /** * Registers a function to implement logic for Charin answerDeleteCertificate */ answerDeleteCertificate<T>(cb: (request: OcppRequest<DeleteCertificatePayload>) => void, options?: AnswerOptions<T>): void { log.debug(LOG_NAME, this.config.cpName, 'answerDeleteCertificate'); this.registeredCallbacks.set("DeleteCertificate", {cb, options}); } /** * Builds the function put into this.registeredCallbacks */ private buildTriggerMessage(): void { const cb = (request: OcppRequest<TriggerMessagePayload>): void => { let requestedMethodRegistered = false; this.registeredCallbacksTriggerMessage.forEach((cb, requestedMessage) => { if (request.payload.requestedMessage === requestedMessage) { requestedMethodRegistered = true; cb(request); } }) if (!requestedMethodRegistered) { this.sendResponse(request.uniqueId, {status: "NotImplemented"}); } }; this.registeredCallbacks.set("TriggerMessage", {cb}); } /** * Builds the function put into this.registeredCallbacks */ private buildExtendedTriggerMessage(): void { const cb = (request: OcppRequest<ExtendedTriggerMessagePayload>): void => { let requestedMethodRegistered = false; this.registeredCallbacksExtendedTriggerMessage.forEach((cb, requestedMessage) => { if (request.payload.requestedMessage === requestedMessage) { requestedMethodRegistered = true; cb(request); } }) if (!requestedMethodRegistered) { this.sendResponse(request.uniqueId, {status: "NotImplemented"}); } }; this.registeredCallbacks.set("ExtendedTriggerMessage", {cb}); } /** * Sends a OCPP diagnostics status notification message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload diagnostics status notification payload object */ sendDiagnosticsStatusNotification(payload: DiagnosticsStatusNotificationPayload): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendDiagnosticsStatusNotification'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'DiagnosticsStatusNotification', payload }); } /** * Sends a OCPP firmware status notification message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload firmware status notification payload object */ sendFirmwareStatusNotification(payload: FirmwareStatusNotificationPayload): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendFirmwareStatusNotification'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'FirmwareStatusNotification', payload }); } /** * Sends a OCPP 1.6 secured sign certificate message. The Promise resolves when the related OCPP response is received and rejects when no response is * received within the timeout period. * * @param payload sign certificate payload object */ sendSignCertificate(payload: SignCertificatePayload): Promise<void> { log.debug(LOG_NAME, this.config.cpName, 'sendSignCertificate'); return this.sendOcpp({ messageTypeId: MessageType.CALL, uniqueId: uuidv4(), action: 'SignCertificate', payload }); } /** * Processes an incoming OCPP message * * @param ocppMessage of any messageTypeId. This is an array of either 2, 3 or 4 elements. */ onMessage(ocppMessage: Array<number | string | object>): void { const messageTypeId = ocppMessage[0] as number; if (messageTypeId === MessageType.CALLRESULT || messageTypeId === MessageType.CALLERROR) { if (ocppMessage[1]) { // this protects against invalid messages this.onMessageResponse({ messageTypeId, uniqueId: ocppMessage[1] as string, payload: ocppMessage[2] as object }); } } else { this.onMessageRequest({ messageTypeId, uniqueId: ocppMessage[1] as string, action: ocppMessage[2] as string, payload: ocppMessage[3] as object }); } } onMessageResponse<T>(ocppResponse: OcppResponse<T>): void { log.debug(LOG_NAME_OCPP, this.config.cpName, ocppResponse); this.sendMsgRemoteConsole(RemoteConsoleTransmissionType.LOG, ocppResponse); this.wsConCentralSystem.triggerRequestResult(ocppResponse); } onMessageRequest<T>(ocppRequest: OcppRequest<T>): void { log.debug(LOG_NAME_OCPP, this.config.cpName, ocppRequest); this.sendMsgRemoteConsole(RemoteConsoleTransmissionType.LOG, ocppRequest); this.registeredCallbacks.forEach(async (ocppRequestWithOptions, action) => { if (action === ocppRequest.action) { let wrappedRequest = ocppRequest; if (ocppRequestWithOptions.options && ocppRequestWithOptions.options.requestConverter) { wrappedRequest = await ocppRequestWithOptions.options.requestConverter(ocppRequest) as OcppRequest<T>; } try { await ocppRequestWithOptions.cb(wrappedRequest); } catch (err) { log.debug(LOG_NAME, this.config.cpName, err); } } }); } /** * Sends an OCPP message to the central system. The promise resolves when the central system sends a CALLRESULT. It rejects when the timeout is due. * * @param req OCPP request object */ private sendOcpp<T, U>(req: OcppRequest<U>): Promise<T> { log.debug(LOG_NAME_OCPP, this.config.cpName, req); this.sendMsgRemoteConsole(RemoteConsoleTransmissionType.LOG, req); return this.wsConCentralSystem.trySendMessageOrDefer(req); } /** * Sends an OCPP response for a previous OCPP request * * @param uniqueId unique-id of the OCPP message * @param payload OCPP payload for this response */ sendResponse(uniqueId: string, payload: object): void { log.debug(LOG_NAME, this.config.cpName, `send-back: ${uniqueId} => ${JSON.stringify(payload)}`); const response = { messageTypeId: MessageType.CALLRESULT, uniqueId, payload } this.sendMsgRemoteConsole(RemoteConsoleTransmissionType.LOG, response); this.wsConCentralSystem.sendResponse(ocppResToArray(response)); log.debug(LOG_NAME_OCPP, this.config.cpName, response); } private sendMsgRemoteConsole(type: RemoteConsoleTransmissionType, payload: string | object) { const wsConRemoteConsoleArr = wsConRemoteConsoleRepository.get(this.config.cpName); wsConRemoteConsoleArr.forEach((wsConRemoteConsole: WSConRemoteConsole) => { wsConRemoteConsole.add(type, payload) }); } private areAnyRemoteConsolesConnected() { return wsConRemoteConsoleRepository.get(this.config.cpName).length > 0; } /** * Close the connection to the central system. */ close(): void { this.wsConCentralSystem.close(); } /** * Upload a dummy file to an FTP location. The promsie will resolve when the file is uploaded. * * @param fileLocation ftp host (and possibly user/password) * @param fileName ftp path and filename */ ftpUploadDummyFile(fileLocation: string, fileName: string): Promise<void> { const ftpSupport = new FtpSupport(); return ftpSupport.ftpUploadDummyFile(fileLocation, fileName); } /** * Download a file from a FTP location. The promise will resolve when the file is downloaded. * * @param fileLocation ftp host (and possibly user/password), path and filename */ ftpDownload(fileLocation: string): Promise<string> { const ftpSupport = new FtpSupport(); return ftpSupport.ftpDownload(fileLocation); } generateCsr(subject: string): Promise<Csr> { const certManagement = new CertManagement(); return certManagement.generateCsr(subject); } convertDerToPem(derHexEncodedCert: string): Promise<string> { const certManagement = new CertManagement(); return certManagement.convertDerToPem(derHexEncodedCert); } keystore(): KeyStore { return this.config.keyStore; } /** * Registers a callback function when the WebSocket to the central system is closed * * @param cb a callback function */ onClose(cb: () => void): void { this.onCloseCb = cb; } /** * Starts a web listener (aka web server), usually used in batch mode only. Use the returned IRouter to add routes. Also the returned IRouter * instance has a method 'terminate()' to gracefully shutdown the web server. * * @param port to bind * @param bind address, default localhost * @param users when given basic authentication is enabled with user: password */ startListener(port: number, bind?: string, users?: { [username: string]: string }): IRouter { const expressInit = express(); expressInit.use(express.json()); const server = http.createServer(expressInit); server.listen(port, bind); server.on('error', (error: NodeJS.ErrnoException) => { console.error(error); }); server.on('listening', () => { const addr = server.address(); log.debug(LOG_NAME, this.config.cpName, `Listening on ${JSON.stringify(addr)}`); }); const httpTerminator = createHttpTerminator({server}) expressInit['terminate'] = (): void => httpTerminator.terminate(); if (users) { expressInit.use(expressBasicAuth({users})); } return expressInit; } /** * Gets AnswerOptions for CertificateSigned operation which converts the hex encoded DER certs to PEM encoding. */ CERTIFICATE_SIGNED_OPTIONS_PEM_ENCODER(): AnswerOptions<CertificateSignedPayload> { return { async requestConverter(resp: OcppRequest<CertificateSignedPayload>): Promise<OcppRequest<CertificateSignedPayload>> { if (resp.payload.cert) { const promisesArr: Array<Promise<string>> = []; const certManagement = new CertManagement(); for (let i = 0; i < resp.payload.cert.length; i++) { promisesArr.push(certManagement.convertDerToPem(resp.payload.cert[i])); } return Promise.all(promisesArr).then(pemEncodedCertsArray => { resp.payload.cert = pemEncodedCertsArray; return resp; }); } else if (resp.payload.messageId == 'CertificateSigned') { const promisesArr: Array<Promise<string>> = []; const certManagement = new CertManagement(); const dataAsObj = typeof resp.payload.data === 'string' ? JSON.parse(resp.payload.data) : resp.payload.data; for (let i = 0; i < dataAsObj.cert.length; i++) { promisesArr.push(certManagement.convertDerToPem(dataAsObj.cert[i])); } return Promise.all(promisesArr).then(pemEncodedCertsArray => { if (typeof resp.payload.data === 'string') { dataAsObj.cert = pemEncodedCertsArray; resp.payload.data = JSON.stringify(dataAsObj); } else { resp.payload.data.cert = pemEncodedCertsArray; } return resp; }); } else { return Promise.resolve(resp); } } } } } /** * Returns a Promise which resolves into a new instance of ChargepointOcpp16Json. Rejects if the connection attempt fails. * * @param url WebSocket Url to connect to */ export function chargepointFactory(url: string, cpName?: string, keyStoreElement?: KeyStoreElement): Promise<ChargepointOcpp16Json> { const wsConCentralSystemFromRepository = wsConCentralSystemRepository.get(cpName); if (wsConCentralSystemFromRepository && wsConCentralSystemFromRepository.readyState === WebSocket.OPEN) { wsConCentralSystemFromRepository.close(); } const cp = new ChargepointOcpp16Json(); return cp.connect(url, cpName, keyStoreElement).then(() => cp); }