X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16IncomingRequestService.ts;h=7c64c2ad20789cfbc0520956b7dfb1addb8cd0ef;hb=cd01b1d8f00898e27f78f4795650a78a179f0d14;hp=69e700a32803a6ec0eb7df41421f4b8fc6f6f881;hpb=a19b897d3aa5cb0f12177a42d8168121ce6ada97;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 69e700a3..7c64c2ad 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -1,26 +1,26 @@ // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. +import { randomInt } from 'node:crypto' import { createWriteStream, readdirSync } from 'node:fs' -import { dirname, join, resolve } from 'node:path' -import { URL, fileURLToPath } from 'node:url' +import { dirname, extname, join, resolve } from 'node:path' +import { fileURLToPath, URL } from 'node:url' -import type { JSONSchemaType } from 'ajv' +import type { ValidateFunction } from 'ajv' import { Client, type FTPResponse } from 'basic-ftp' import { - type Interval, addSeconds, differenceInSeconds, + type Interval, isDate, secondsToMilliseconds } from 'date-fns' import { maxTime } from 'date-fns/constants' +import { isEmpty } from 'rambda' import { create } from 'tar' -import { OCPP16Constants } from './OCPP16Constants.js' -import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js' import { - type ChargingStation, canProceedChargingProfile, + type ChargingStation, checkChargingStation, getConfigurationKey, getConnectorChargingProfiles, @@ -32,6 +32,7 @@ import { OCPPError } from '../../../exception/index.js' import { type ChangeConfigurationRequest, type ChangeConfigurationResponse, + ConfigurationSection, ErrorType, type GenericResponse, GenericStatus, @@ -41,6 +42,7 @@ import { type GetDiagnosticsResponse, type IncomingRequestHandler, type JsonType, + type LogConfiguration, OCPP16AuthorizationStatus, OCPP16AvailabilityType, type OCPP16BootNotificationRequest, @@ -83,6 +85,7 @@ import { OCPP16SupportedFeatureProfiles, type OCPP16TriggerMessageRequest, type OCPP16TriggerMessageResponse, + OCPP16TriggerMessageStatus, type OCPP16UpdateFirmwareRequest, type OCPP16UpdateFirmwareResponse, type OCPPConfigurationKey, @@ -97,23 +100,26 @@ import { type UnlockConnectorResponse } from '../../../types/index.js' import { + Configuration, Constants, convertToDate, convertToInt, formatDurationMilliSeconds, - getRandomInteger, - isEmptyArray, + isAsyncFunction, isNotEmptyArray, isNotEmptyString, logger, sleep } from '../../../utils/index.js' import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js' +import { OCPP16Constants } from './OCPP16Constants.js' +import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js' const moduleName = 'OCPP16IncomingRequestService' export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { - protected jsonSchemas: Map> + protected payloadValidateFunctions: Map> + private readonly incomingRequestHandlers: Map< OCPP16IncomingRequestCommand, IncomingRequestHandler @@ -194,149 +200,404 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler ] ]) - this.jsonSchemas = new Map>([ + this.payloadValidateFunctions = new Map< + OCPP16IncomingRequestCommand, + ValidateFunction + >([ [ OCPP16IncomingRequestCommand.RESET, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/Reset.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/Reset.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.CLEAR_CACHE, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/ClearCache.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ClearCache.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/UnlockConnector.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/UnlockConnector.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.GET_CONFIGURATION, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/GetConfiguration.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/GetConfiguration.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/TriggerMessage.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/TriggerMessage.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.DATA_TRANSFER, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/DataTransfer.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/DataTransfer.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.RESERVE_NOW, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/ReserveNow.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ReserveNow.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ], [ OCPP16IncomingRequestCommand.CANCEL_RESERVATION, - OCPP16ServiceUtils.parseJsonSchemaFile( - 'assets/json-schemas/ocpp/1.6/CancelReservation.json', - moduleName, - 'constructor' - ) + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/CancelReservation.json', + moduleName, + 'constructor' + ) + ) + .bind(this) ] ]) - this.validatePayload = this.validatePayload.bind(this) as ( - chargingStation: ChargingStation, - commandName: OCPP16IncomingRequestCommand, - commandPayload: JsonType - ) => boolean + // 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, 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: unknown) => { + 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: unknown) => { + 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: unknown): void => { + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`, + error + ) + } + switch (requestedMessage) { + case OCPP16MessageTrigger.BootNotification: + chargingStation.ocppRequestService + .requestHandler( + chargingStation, + OCPP16RequestCommand.BOOT_NOTIFICATION, + chargingStation.bootNotificationRequest as OCPP16BootNotificationRequest, + { skipBufferingOnError: true, triggerMessage: true } + ) + .then(response => { + chargingStation.bootNotificationResponse = response + }) + .catch(errorHandler) + break + case OCPP16MessageTrigger.Heartbeat: + chargingStation.ocppRequestService + .requestHandler( + chargingStation, + OCPP16RequestCommand.HEARTBEAT, + undefined, + { + triggerMessage: true + } + ) + .catch(errorHandler) + break + case OCPP16MessageTrigger.StatusNotification: + if (connectorId != null) { + chargingStation.ocppRequestService + .requestHandler( + chargingStation, + OCPP16RequestCommand.STATUS_NOTIFICATION, + { + connectorId, + errorCode: OCPP16ChargePointErrorCode.NO_ERROR, + status: chargingStation.getConnectorStatus(connectorId) + ?.status as OCPP16ChargePointStatus + }, + { + 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 as OCPP16ChargePointStatus + }, + { + 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 as OCPP16ChargePointStatus + }, + { + triggerMessage: true + } + ) + .catch(errorHandler) + } + } + break + } + } + ) + this.validatePayload = this.validatePayload.bind(this) } public async incomingRequestHandler( @@ -376,10 +637,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { this.validatePayload(chargingStation, commandName, commandPayload) // Call the method to build the response // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - response = (await this.incomingRequestHandlers.get(commandName)!( - chargingStation, - commandPayload - )) as ResType + 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( @@ -392,7 +655,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { // Throw exception throw new OCPPError( ErrorType.NOT_IMPLEMENTED, - `${commandName} is not implemented to handle request PDU ${JSON.stringify( + `'${commandName}' is not implemented to handle request PDU ${JSON.stringify( commandPayload, undefined, 2 @@ -408,7 +671,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { commandPayload, undefined, 2 - )} while the charging station is not registered on the central server.`, + )} while the charging station is not registered on the central server`, commandName, commandPayload ) @@ -420,6 +683,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { response, commandName ) + // Emit command name event to allow delayed handling + this.emit(commandName, chargingStation, commandPayload, response) } private validatePayload ( @@ -427,17 +692,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { commandName: OCPP16IncomingRequestCommand, commandPayload: JsonType ): boolean { - if (this.jsonSchemas.has(commandName)) { - return this.validateIncomingRequestPayload( - chargingStation, - commandName, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.jsonSchemas.get(commandName)!, - commandPayload - ) + if (this.payloadValidateFunctions.has(commandName)) { + return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload) } logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation` + `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation` ) return false } @@ -693,8 +952,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { } const connectorStatus = chargingStation.getConnectorStatus(connectorId) if ( - isEmptyArray(connectorStatus?.chargingProfiles) && - isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles) + isEmpty(connectorStatus?.chargingProfiles) && + isEmpty(chargingStation.getConnectorStatus(0)?.chargingProfiles) ) { return OCPP16Constants.OCPP_RESPONSE_REJECTED } @@ -726,10 +985,9 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingProfile.chargingProfileId } startSchedule property is not a Date instance. Trying to convert it to a Date instance` ) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion chargingProfile.chargingSchedule.startSchedule = convertToDate( chargingProfile.chargingSchedule.startSchedule - )! + ) } if (chargingProfile.chargingSchedule.duration == null) { logger.debug( @@ -740,7 +998,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { // OCPP specifies that if duration is not defined, it should be infinite chargingProfile.chargingSchedule.duration = differenceInSeconds( maxTime, - chargingProfile.chargingSchedule.startSchedule + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingProfile.chargingSchedule.startSchedule! ) } if ( @@ -903,7 +1162,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ): Promise { const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload if (!chargingStation.hasConnector(transactionConnectorId)) { - return await this.notifyRemoteStartTransactionRejected( + return this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, idTag @@ -913,118 +1172,57 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { !chargingStation.isChargingStationAvailable() || !chargingStation.isConnectorAvailable(transactionConnectorId) ) { - return await this.notifyRemoteStartTransactionRejected( + return this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, idTag ) } - const remoteStartTransactionLogMsg = ` - ${chargingStation.logPrefix()} Transaction remotely STARTED on ${ - chargingStation.stationInfo?.chargingStationId - }#${transactionConnectorId} for idTag '${idTag}'` - await OCPP16ServiceUtils.sendAndSetConnectorStatus( - chargingStation, - transactionConnectorId, - OCPP16ChargePointStatus.Preparing - ) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)! - // Authorization check required + // idTag authorization check required if ( chargingStation.getAuthorizeRemoteTxRequests() && - (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag)) + !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag)) ) { - // Authorization successful, start transaction - if ( - (chargingProfile != null && - this.setRemoteStartTransactionChargingProfile( - chargingStation, - transactionConnectorId, - chargingProfile - )) || - chargingProfile == null - ) { - connectorStatus.transactionRemoteStarted = true - if ( - ( - await chargingStation.ocppRequestService.requestHandler< - OCPP16StartTransactionRequest, - OCPP16StartTransactionResponse - >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, { - connectorId: transactionConnectorId, - idTag - }) - ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED - ) { - logger.debug(remoteStartTransactionLogMsg) - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED - } - return await this.notifyRemoteStartTransactionRejected( - chargingStation, - transactionConnectorId, - idTag - ) - } - return await this.notifyRemoteStartTransactionRejected( + return this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, idTag ) } - // No authorization check required, start transaction if ( - (chargingProfile != null && - this.setRemoteStartTransactionChargingProfile( - chargingStation, - transactionConnectorId, - chargingProfile - )) || - chargingProfile == null + chargingProfile != null && + !this.setRemoteStartTransactionChargingProfile( + chargingStation, + transactionConnectorId, + chargingProfile + ) ) { - connectorStatus.transactionRemoteStarted = true - if ( - ( - await chargingStation.ocppRequestService.requestHandler< - OCPP16StartTransactionRequest, - OCPP16StartTransactionResponse - >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, { - connectorId: transactionConnectorId, - idTag - }) - ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED - ) { - logger.debug(remoteStartTransactionLogMsg) - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED - } - return await this.notifyRemoteStartTransactionRejected( + return this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, idTag ) } - return await this.notifyRemoteStartTransactionRejected( - chargingStation, - transactionConnectorId, - idTag + logger.debug( + `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${ + chargingStation.stationInfo?.chargingStationId + }#${transactionConnectorId}}, idTag '${idTag}'` ) + return OCPP16Constants.OCPP_RESPONSE_ACCEPTED } - private async notifyRemoteStartTransactionRejected ( + private notifyRemoteStartTransactionRejected ( chargingStation: ChargingStation, connectorId: number, idTag: string - ): Promise { + ): GenericResponse { const connectorStatus = chargingStation.getConnectorStatus(connectorId) - if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) { - await OCPP16ServiceUtils.sendAndSetConnectorStatus( - chargingStation, - connectorId, - OCPP16ChargePointStatus.Available - ) - } - logger.warn( - `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'` + logger.debug( + `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${ + chargingStation.stationInfo?.chargingStationId + }#${connectorId}, idTag '${idTag}', availability '${ + connectorStatus?.availability + }', status '${connectorStatus?.status}'` ) return OCPP16Constants.OCPP_RESPONSE_REJECTED } @@ -1037,12 +1235,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) { OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile) logger.debug( - `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`, + `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${ + chargingStation.stationInfo?.chargingStationId + }#${connectorId}`, chargingProfile ) return true } - logger.warn( + logger.debug( `${chargingStation.logPrefix()} Not allowed to set ${ chargingProfile.chargingProfilePurpose } charging profile(s) at remote start transaction` @@ -1050,33 +1250,19 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { return false } - private async handleRequestRemoteStopTransaction ( + private handleRequestRemoteStopTransaction ( chargingStation: ChargingStation, commandPayload: RemoteStopTransactionRequest - ): Promise { + ): GenericResponse { const { transactionId } = commandPayload - if (chargingStation.hasEvses) { - for (const [evseId, evseStatus] of chargingStation.evses) { - if (evseId > 0) { - for (const [connectorId, connectorStatus] of evseStatus.connectors) { - if (connectorStatus.transactionId === transactionId) { - return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId) - } - } - } - } - } else { - for (const connectorId of chargingStation.connectors.keys()) { - if ( - connectorId > 0 && - chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId - ) { - return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId) - } - } + if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) { + logger.debug( + `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'` + ) + return OCPP16Constants.OCPP_RESPONSE_ACCEPTED } - logger.warn( - `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}` + logger.debug( + `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'` ) return OCPP16Constants.OCPP_RESPONSE_REJECTED } @@ -1165,7 +1351,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingStation.stationInfo?.firmwareUpgrade?.failureStatus === OCPP16FirmwareStatus.DownloadFailed ) { - await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) + await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))) await chargingStation.ocppRequestService.requestHandler< OCPP16FirmwareStatusNotificationRequest, OCPP16FirmwareStatusNotificationResponse @@ -1176,7 +1362,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingStation.stationInfo.firmwareUpgrade.failureStatus return } - await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) + await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))) await chargingStation.ocppRequestService.requestHandler< OCPP16FirmwareStatusNotificationRequest, OCPP16FirmwareStatusNotificationResponse @@ -1232,8 +1418,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { transactionsStarted = false } } while (transactionsStarted) - !wasTransactionsStarted && - (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))) + !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))) if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) { return } @@ -1249,7 +1434,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingStation.stationInfo?.firmwareUpgrade?.failureStatus === OCPP16FirmwareStatus.InstallationFailed ) { - await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) + await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))) await chargingStation.ocppRequestService.requestHandler< OCPP16FirmwareStatusNotificationRequest, OCPP16FirmwareStatusNotificationResponse @@ -1261,7 +1446,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { return } if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) { - await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) + await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))) await chargingStation.reset(OCPP16StopTransactionReason.REBOOT) } } @@ -1287,14 +1472,22 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (uri.protocol.startsWith('ftp:')) { let ftpClient: Client | undefined try { - const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../')) - .filter(file => file.endsWith('.log')) - .map(file => join('./', file)) + const logConfiguration = Configuration.getConfigurationSection( + ConfigurationSection.log + ) + const logFiles = readdirSync( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + resolve((fileURLToPath(import.meta.url), '../', dirname(logConfiguration.file!))) + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .filter(file => file.endsWith(extname(logConfiguration.file!))) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .map(file => join(dirname(logConfiguration.file!), file)) const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz` create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive)) ftpClient = new Client() const accessResponse = await ftpClient.access({ - host: uri.host, + host: uri.hostname, ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }), ...(isNotEmptyString(uri.username) && { user: uri.username }), ...(isNotEmptyString(uri.password) && { password: uri.password }) @@ -1314,7 +1507,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { status: OCPP16DiagnosticsStatus.Uploading }) - .catch(error => { + .catch((error: unknown) => { logger.error( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${ OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION @@ -1405,110 +1598,13 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ) { return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED } - try { - switch (requestedMessage) { - case OCPP16MessageTrigger.BootNotification: - setTimeout(() => { - chargingStation.ocppRequestService - .requestHandler( - chargingStation, - OCPP16RequestCommand.BOOT_NOTIFICATION, - chargingStation.bootNotificationRequest, - { skipBufferingOnError: true, triggerMessage: true } - ) - .then(response => { - chargingStation.bootNotificationResponse = response - }) - .catch(Constants.EMPTY_FUNCTION) - }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY) - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED - case OCPP16MessageTrigger.Heartbeat: - setTimeout(() => { - chargingStation.ocppRequestService - .requestHandler( - chargingStation, - OCPP16RequestCommand.HEARTBEAT, - undefined, - { - triggerMessage: true - } - ) - .catch(Constants.EMPTY_FUNCTION) - }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY) - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED - case OCPP16MessageTrigger.StatusNotification: - setTimeout(() => { - if (connectorId != null) { - chargingStation.ocppRequestService - .requestHandler( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: chargingStation.getConnectorStatus(connectorId)?.status - }, - { - triggerMessage: true - } - ) - .catch(Constants.EMPTY_FUNCTION) - } 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(Constants.EMPTY_FUNCTION) - } - } - } 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(Constants.EMPTY_FUNCTION) - } - } - }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY) - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED - default: - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED - } - } catch (error) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.handleIncomingRequestError( - chargingStation, - OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, - error as Error, - { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED } - )! + switch (requestedMessage) { + case OCPP16MessageTrigger.BootNotification: + case OCPP16MessageTrigger.Heartbeat: + case OCPP16MessageTrigger.StatusNotification: + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED + default: + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED } } @@ -1640,7 +1736,9 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingStation, OCPP16IncomingRequestCommand.CANCEL_RESERVATION, error as Error, - { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } + { + errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED + } )! } }