+const moduleName = 'OCPP16IncomingRequestService'
+
+export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
+ protected payloadValidateFunctions: Map<OCPP16IncomingRequestCommand, ValidateFunction<JsonType>>
+
+ private readonly incomingRequestHandlers: Map<
+ OCPP16IncomingRequestCommand,
+ IncomingRequestHandler
+ >
+
+ public constructor () {
+ // if (new.target.name === moduleName) {
+ // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
+ // }
+ super(OCPPVersion.VERSION_16)
+ this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
+ [
+ OCPP16IncomingRequestCommand.RESET,
+ this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.CLEAR_CACHE,
+ this.handleRequestClearCache.bind(this) as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+ this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+ this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+ this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
+ this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+ this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+ this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+ this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+ this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+ this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+ this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+ this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.DATA_TRANSFER,
+ this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
+ this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.RESERVE_NOW,
+ this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler
+ ],
+ [
+ OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+ this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler
+ ]
+ ])
+ this.payloadValidateFunctions = new Map<
+ OCPP16IncomingRequestCommand,
+ ValidateFunction<JsonType>
+ >([
+ [
+ OCPP16IncomingRequestCommand.RESET,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
+ 'assets/json-schemas/ocpp/1.6/Reset.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.CLEAR_CACHE,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
+ 'assets/json-schemas/ocpp/1.6/ClearCache.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
+ 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
+ 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
+ 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
+ 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
+ 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
+ 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
+ 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
+ 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
+ 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
+ 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
+ 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.DATA_TRANSFER,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
+ 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
+ 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.RESERVE_NOW,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
+ 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ],
+ [
+ OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+ this.ajv
+ .compile(
+ OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
+ 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
+ moduleName,
+ 'constructor'
+ )
+ )
+ .bind(this)
+ ]
+ ])
+ // Handle incoming request events
+ this.on(
+ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+ (
+ chargingStation: ChargingStation,
+ request: RemoteStartTransactionRequest,
+ response: GenericResponse
+ ) => {
+ if (response.status === GenericStatus.Accepted) {
+ const { connectorId, idTag } = request
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true
+ chargingStation.ocppRequestService
+ .requestHandler<OCPP16StartTransactionRequest, OCPP16StartTransactionResponse>(
+ chargingStation,
+ OCPP16RequestCommand.START_TRANSACTION,
+ {
+ connectorId,
+ idTag
+ }
+ )
+ .then(response => {
+ if (response.status === OCPP16AuthorizationStatus.ACCEPTED) {
+ logger.debug(
+ `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
+ )
+ } else {
+ logger.debug(
+ `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
+ )
+ }
+ })
+ .catch(error => {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
+ error
+ )
+ })
+ }
+ }
+ )
+ this.on(
+ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+ (
+ chargingStation: ChargingStation,
+ request: RemoteStopTransactionRequest,
+ response: GenericResponse
+ ) => {
+ if (response.status === GenericStatus.Accepted) {
+ const { transactionId } = request
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)!
+ OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
+ .then(response => {
+ if (response.status === GenericStatus.Accepted) {
+ logger.debug(
+ `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for transaction '${transactionId}'`
+ )
+ } else {
+ logger.debug(
+ `${chargingStation.logPrefix()} Remote stop transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for transaction '${transactionId}'`
+ )
+ }
+ })
+ .catch(error => {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote stop transaction error:`,
+ error
+ )
+ })
+ }
+ }
+ )
+ this.on(
+ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+ (
+ chargingStation: ChargingStation,
+ request: OCPP16TriggerMessageRequest,
+ response: OCPP16TriggerMessageResponse
+ ) => {
+ if (response.status !== OCPP16TriggerMessageStatus.ACCEPTED) {
+ return
+ }
+ const { requestedMessage, connectorId } = request
+ const errorHandler = (error: Error): void => {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
+ error
+ )
+ }
+ switch (requestedMessage) {
+ case OCPP16MessageTrigger.BootNotification:
+ chargingStation.ocppRequestService
+ .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
+ chargingStation,
+ OCPP16RequestCommand.BOOT_NOTIFICATION,
+ chargingStation.bootNotificationRequest,
+ { skipBufferingOnError: true, triggerMessage: true }
+ )
+ .then(response => {
+ chargingStation.bootNotificationResponse = response
+ })
+ .catch(errorHandler)
+ break
+ case OCPP16MessageTrigger.Heartbeat:
+ chargingStation.ocppRequestService
+ .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
+ chargingStation,
+ OCPP16RequestCommand.HEARTBEAT,
+ undefined,
+ {
+ triggerMessage: true
+ }
+ )
+ .catch(errorHandler)
+ break
+ case OCPP16MessageTrigger.StatusNotification:
+ if (connectorId != null) {
+ chargingStation.ocppRequestService
+ .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
+ chargingStation,
+ OCPP16RequestCommand.STATUS_NOTIFICATION,
+ {
+ connectorId,
+ errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+ status: chargingStation.getConnectorStatus(connectorId)?.status
+ },
+ {
+ triggerMessage: true
+ }
+ )
+ .catch(errorHandler)
+ } else if (chargingStation.hasEvses) {
+ for (const evseStatus of chargingStation.evses.values()) {
+ for (const [id, connectorStatus] of evseStatus.connectors) {
+ chargingStation.ocppRequestService
+ .requestHandler<
+ OCPP16StatusNotificationRequest,
+ OCPP16StatusNotificationResponse
+ >(
+ chargingStation,
+ OCPP16RequestCommand.STATUS_NOTIFICATION,
+ {
+ connectorId: id,
+ errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+ status: connectorStatus.status
+ },
+ {
+ triggerMessage: true
+ }
+ )
+ .catch(errorHandler)
+ }
+ }
+ } else {
+ for (const [id, connectorStatus] of chargingStation.connectors) {
+ chargingStation.ocppRequestService
+ .requestHandler<
+ OCPP16StatusNotificationRequest,
+ OCPP16StatusNotificationResponse
+ >(
+ chargingStation,
+ OCPP16RequestCommand.STATUS_NOTIFICATION,
+ {
+ connectorId: id,
+ errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+ status: connectorStatus.status
+ },
+ {
+ triggerMessage: true
+ }
+ )
+ .catch(errorHandler)
+ }
+ }
+ break
+ }
+ }
+ )
+ this.validatePayload = this.validatePayload.bind(this)
+ }
+
+ public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
+ chargingStation: ChargingStation,
+ messageId: string,
+ commandName: OCPP16IncomingRequestCommand,
+ commandPayload: ReqType
+ ): Promise<void> {
+ let response: ResType
+ if (
+ chargingStation.stationInfo?.ocppStrictCompliance === true &&
+ chargingStation.inPendingState() &&
+ (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
+ commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
+ ) {
+ throw new OCPPError(
+ ErrorType.SECURITY_ERROR,
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
+ commandPayload,
+ undefined,
+ 2
+ )} while the charging station is in pending state on the central server`,
+ commandName,
+ commandPayload
+ )
+ }
+ if (
+ chargingStation.isRegistered() ||
+ (chargingStation.stationInfo?.ocppStrictCompliance === false &&
+ chargingStation.inUnknownState())
+ ) {
+ if (
+ this.incomingRequestHandlers.has(commandName) &&
+ OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
+ ) {
+ try {
+ this.validatePayload(chargingStation, commandName, commandPayload)
+ // Call the method to build the response
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
+ if (isAsyncFunction(incomingRequestHandler)) {
+ response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
+ } else {
+ response = incomingRequestHandler(chargingStation, commandPayload) as ResType
+ }
+ } catch (error) {
+ // Log
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
+ error
+ )
+ throw error
+ }
+ } else {
+ // Throw exception
+ throw new OCPPError(
+ ErrorType.NOT_IMPLEMENTED,
+ `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
+ commandPayload,
+ undefined,
+ 2
+ )}`,
+ commandName,
+ commandPayload
+ )