X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16IncomingRequestService.ts;h=2af366f9cfbd4ac6945dc6262352e8493a3c98eb;hb=f2f33b97eb2044a8c715ebe0c0eb1c7b973eb8c4;hp=94f03ef8b26a4442c34a120212a8775b87866998;hpb=a807045be19c1ed4996a44d8c2c8774e926dc6dc;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 94f03ef8..2af366f9 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -1,10 +1,10 @@ -// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. +// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. import { createWriteStream, readdirSync } from 'node:fs' import { dirname, join, resolve } from 'node:path' import { URL, fileURLToPath } from 'node:url' -import type { JSONSchemaType } from 'ajv' +import type { ValidateFunction } from 'ajv' import { Client, type FTPResponse } from 'basic-ftp' import { type Interval, @@ -105,8 +105,6 @@ import { isEmptyArray, isNotEmptyArray, isNotEmptyString, - isNullOrUndefined, - isUndefined, logger, sleep } from '../../../utils/index.js' @@ -115,15 +113,16 @@ import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js' const moduleName = 'OCPP16IncomingRequestService' export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { - protected jsonSchemas: Map> + protected payloadValidateFunctions: Map> + private readonly incomingRequestHandlers: Map< OCPP16IncomingRequestCommand, IncomingRequestHandler > public constructor () { - // if (new.target?.name === moduleName) { - // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`) + // if (new.target.name === moduleName) { + // throw new TypeError(`Cannot construct ${new.target.name} instances directly`) // } super(OCPPVersion.VERSION_16) this.incomingRequestHandlers = new Map([ @@ -196,149 +195,350 @@ 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 + this.on( + OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, + (chargingStation: ChargingStation, connectorId: number, idTag: string) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true + chargingStation.ocppRequestService + .requestHandler( + 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( + `Trigger${OCPP16MessageTrigger.BootNotification}`, + (chargingStation: ChargingStation) => { + chargingStation.ocppRequestService + .requestHandler( + chargingStation, + OCPP16RequestCommand.BOOT_NOTIFICATION, + chargingStation.bootNotificationRequest, + { skipBufferingOnError: true, triggerMessage: true } + ) + .then(response => { + chargingStation.bootNotificationResponse = response + }) + .catch(error => { + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger boot notification error:`, + error + ) + }) + } + ) + this.on(`Trigger${OCPP16MessageTrigger.Heartbeat}`, (chargingStation: ChargingStation) => { + chargingStation.ocppRequestService + .requestHandler( + chargingStation, + OCPP16RequestCommand.HEARTBEAT, + undefined, + { + triggerMessage: true + } + ) + .catch(error => { + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger heartbeat error:`, + error + ) + }) + }) + this.on( + `$Trigger${OCPP16MessageTrigger.StatusNotification}`, + (chargingStation: ChargingStation, connectorId?: number) => { + const errorHandler = (error: Error): void => { + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger status notification error:`, + error + ) + } + if (connectorId != null) { + chargingStation.ocppRequestService + .requestHandler( + 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( + 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( + chargingStation, + OCPP16RequestCommand.STATUS_NOTIFICATION, + { + connectorId: id, + errorCode: OCPP16ChargePointErrorCode.NO_ERROR, + status: connectorStatus.status + }, + { + triggerMessage: true + } + ) + .catch(errorHandler) + } + } + } + ) + this.validatePayload = this.validatePayload.bind(this) } public async incomingRequestHandler( @@ -429,17 +629,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 } @@ -456,7 +650,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { logger.info( `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - chargingStation.stationInfo.resetTime! + chargingStation.stationInfo!.resetTime! )}` ) return OCPP16Constants.OCPP_RESPONSE_ACCEPTED @@ -502,30 +696,23 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { const { key } = commandPayload const configurationKey: OCPPConfigurationKey[] = [] const unknownKey: string[] = [] - if (isUndefined(key)) { + if (key == null) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) { - if (isUndefined(configuration.visible)) { - configuration.visible = true - } - if (configuration.visible === false) { + for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) { + if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) { continue } configurationKey.push({ - key: configuration.key, - readonly: configuration.readonly, - value: configuration.value + key: configKey.key, + readonly: configKey.readonly, + value: configKey.value }) } } else if (isNotEmptyArray(key)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - for (const k of key!) { + for (const k of key) { const keyFound = getConfigurationKey(chargingStation, k, true) if (keyFound != null) { - if (isUndefined(keyFound.visible)) { - keyFound.visible = true - } - if (keyFound.visible === false) { + if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) { continue } configurationKey.push({ @@ -652,12 +839,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && connectorId > 0 && connectorStatus?.transactionStarted === true && - csChargingProfiles.transactionId !== connectorStatus?.transactionId + csChargingProfiles.transactionId !== connectorStatus.transactionId ) { logger.error( `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${ csChargingProfiles.transactionId - } than the started transaction id ${connectorStatus?.transactionId}` + } than the started transaction id ${connectorStatus.transactionId}` ) return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED } @@ -700,13 +887,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done` ) } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const connectorStatus = chargingStation.getConnectorStatus(connectorId)! + const connectorStatus = chargingStation.getConnectorStatus(connectorId) if ( - isEmptyArray( - connectorStatus?.chargingProfiles != null && - isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles) - ) + isEmptyArray(connectorStatus?.chargingProfiles) && + isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles) ) { return OCPP16Constants.OCPP_RESPONSE_REJECTED } @@ -723,10 +907,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { let previousCompositeSchedule: OCPP16ChargingSchedule | undefined let compositeSchedule: OCPP16ChargingSchedule | undefined for (const chargingProfile of chargingProfiles) { - if ( - isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) && - connectorStatus?.transactionStarted === true - ) { + if (chargingProfile.chargingSchedule.startSchedule == null) { logger.debug( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ chargingProfile.chargingProfileId @@ -735,24 +916,17 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart } - if ( - !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) && - !isDate(chargingProfile.chargingSchedule?.startSchedule) - ) { + if (!isDate(chargingProfile.chargingSchedule.startSchedule)) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ 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 - )! + chargingProfile.chargingSchedule.startSchedule + ) } - if ( - !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) && - isNullOrUndefined(chargingProfile.chargingSchedule?.duration) - ) { + if (chargingProfile.chargingSchedule.duration == null) { logger.debug( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ chargingProfile.chargingProfileId @@ -769,7 +943,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { !prepareChargingProfileKind( connectorStatus, chargingProfile, - compositeScheduleInterval.start as Date, + compositeScheduleInterval.start, chargingStation.logPrefix() ) ) { @@ -778,7 +952,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if ( !canProceedChargingProfile( chargingProfile, - compositeScheduleInterval.start as Date, + compositeScheduleInterval.start, chargingStation.logPrefix() ) ) { @@ -794,8 +968,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (compositeSchedule != null) { return { status: GenericStatus.Accepted, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - scheduleStart: compositeSchedule.startSchedule!, + scheduleStart: compositeSchedule.startSchedule, connectorId, chargingSchedule: compositeSchedule } @@ -826,15 +999,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const connectorStatus = chargingStation.getConnectorStatus(connectorId!) - if (!isNullOrUndefined(connectorId) && isNotEmptyArray(connectorStatus?.chargingProfiles)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - connectorStatus!.chargingProfiles = [] + if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) { + connectorStatus.chargingProfiles = [] logger.debug( `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}` ) return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED } - if (isNullOrUndefined(connectorId)) { + if (connectorId == null) { let clearedCP = false if (chargingStation.hasEvses) { for (const evseStatus of chargingStation.evses.values()) { @@ -878,7 +1050,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ? OCPP16ChargePointStatus.Available : OCPP16ChargePointStatus.Unavailable if (connectorId === 0) { - let response: OCPP16ChangeAvailabilityResponse + let response: OCPP16ChangeAvailabilityResponse | undefined if (chargingStation.hasEvses) { for (const evseStatus of chargingStation.evses.values()) { response = await OCPP16ServiceUtils.changeAvailability( @@ -943,95 +1115,47 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { 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( chargingStation, transactionConnectorId, idTag ) } - // No authorization check required, start transaction + await OCPP16ServiceUtils.sendAndSetConnectorStatus( + chargingStation, + transactionConnectorId, + OCPP16ChargePointStatus.Preparing + ) 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( chargingStation, transactionConnectorId, idTag ) } - return await this.notifyRemoteStartTransactionRejected( - chargingStation, - transactionConnectorId, - idTag - ) + Promise.resolve() + .then(() => + this.emit( + OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, + chargingStation, + transactionConnectorId, + idTag + ) + ) + .catch(Constants.EMPTY_FUNCTION) + return OCPP16Constants.OCPP_RESPONSE_ACCEPTED } private async notifyRemoteStartTransactionRejected ( @@ -1048,7 +1172,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ) } logger.warn( - `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'` + `${chargingStation.logPrefix()} Remote start transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'` ) return OCPP16Constants.OCPP_RESPONSE_REJECTED } @@ -1058,7 +1182,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { connectorId: number, chargingProfile: OCPP16ChargingProfile ): boolean { - if (chargingProfile?.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) { + 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`, @@ -1121,28 +1245,22 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ) return OCPP16Constants.OCPP_RESPONSE_EMPTY } - let { retrieveDate } = commandPayload - if ( - !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) && - chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed - ) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)! + const { retrieveDate } = commandPayload + if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress` ) return OCPP16Constants.OCPP_RESPONSE_EMPTY } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - retrieveDate = convertToDate(retrieveDate)! const now = Date.now() - if (retrieveDate?.getTime() <= now) { + if (retrieveDate.getTime() <= now) { this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION) } else { - setTimeout( - () => { - this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION) - }, - retrieveDate?.getTime() - now - ) + setTimeout(() => { + this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION) + }, retrieveDate.getTime() - now) } return OCPP16Constants.OCPP_RESPONSE_EMPTY } @@ -1159,7 +1277,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { for (const [evseId, evseStatus] of chargingStation.evses) { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { - if (connectorStatus?.transactionStarted === false) { + if (connectorStatus.transactionStarted === false) { await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, @@ -1189,7 +1307,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { status: OCPP16FirmwareStatus.Downloading }) - chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading if ( chargingStation.stationInfo?.firmwareUpgrade?.failureStatus === OCPP16FirmwareStatus.DownloadFailed @@ -1199,10 +1318,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { OCPP16FirmwareStatusNotificationRequest, OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus + status: chargingStation.stationInfo.firmwareUpgrade.failureStatus }) chargingStation.stationInfo.firmwareStatus = - chargingStation.stationInfo?.firmwareUpgrade?.failureStatus + chargingStation.stationInfo.firmwareUpgrade.failureStatus return } await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) @@ -1212,7 +1331,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { status: OCPP16FirmwareStatus.Downloaded }) - chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded let wasTransactionsStarted = false let transactionsStarted: boolean do { @@ -1232,7 +1352,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { for (const [evseId, evseStatus] of chargingStation.evses) { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { - if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) { + if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) { await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, @@ -1271,7 +1391,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { status: OCPP16FirmwareStatus.Installing }) - chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing if ( chargingStation.stationInfo?.firmwareUpgrade?.failureStatus === OCPP16FirmwareStatus.InstallationFailed @@ -1281,10 +1402,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { OCPP16FirmwareStatusNotificationRequest, OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus + status: chargingStation.stationInfo.firmwareUpgrade.failureStatus }) chargingStation.stationInfo.firmwareStatus = - chargingStation.stationInfo?.firmwareUpgrade?.failureStatus + chargingStation.stationInfo.firmwareUpgrade.failureStatus return } if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) { @@ -1315,9 +1436,9 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { let ftpClient: Client | undefined try { const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../')) - .filter((file) => file.endsWith('.log')) - .map((file) => join('./', file)) - const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz` + .filter(file => file.endsWith('.log')) + .map(file => join('./', file)) + const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz` create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive)) ftpClient = new Client() const accessResponse = await ftpClient.access({ @@ -1328,7 +1449,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { }) let uploadResponse: FTPResponse | undefined if (accessResponse.code === 220) { - ftpClient.trackProgress((info) => { + ftpClient.trackProgress(info => { logger.info( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${ info.bytes / 1024 @@ -1341,7 +1462,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { status: OCPP16DiagnosticsStatus.Uploading }) - .catch((error) => { + .catch(error => { logger.error( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${ OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION @@ -1361,24 +1482,18 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { status: OCPP16DiagnosticsStatus.Uploaded }) - if (ftpClient != null) { - ftpClient.close() - } + ftpClient.close() return { fileName: diagnosticsArchive } } throw new OCPPError( ErrorType.GENERIC_ERROR, - `Diagnostics transfer failed with error code ${accessResponse.code}${ - uploadResponse?.code != null && `|${uploadResponse?.code}` - }`, + `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS ) } throw new OCPPError( ErrorType.GENERIC_ERROR, - `Diagnostics transfer failed with error code ${accessResponse.code}${ - uploadResponse?.code != null && `|${uploadResponse?.code}` - }`, + `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS ) } catch (error) { @@ -1388,9 +1503,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { status: OCPP16DiagnosticsStatus.UploadFailed }) - if (ftpClient != null) { - ftpClient.close() - } + ftpClient?.close() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.handleIncomingRequestError( chargingStation, @@ -1440,113 +1553,30 @@ 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( + switch (requestedMessage) { + case OCPP16MessageTrigger.BootNotification: + Promise.resolve() + .then(() => this.emit(`Trigger${OCPP16MessageTrigger.BootNotification}`, chargingStation)) + .catch(Constants.EMPTY_FUNCTION) + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED + case OCPP16MessageTrigger.Heartbeat: + Promise.resolve() + .then(() => this.emit(`Trigger${OCPP16MessageTrigger.Heartbeat}`, chargingStation)) + .catch(Constants.EMPTY_FUNCTION) + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED + case OCPP16MessageTrigger.StatusNotification: + Promise.resolve() + .then(() => + this.emit( + `Trigger${OCPP16MessageTrigger.StatusNotification}`, chargingStation, - OCPP16RequestCommand.HEARTBEAT, - undefined, - { - triggerMessage: true - } + connectorId ) - .catch(Constants.EMPTY_FUNCTION) - }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY) - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED - case OCPP16MessageTrigger.StatusNotification: - setTimeout(() => { - if (!isNullOrUndefined(connectorId)) { - chargingStation.ocppRequestService - .requestHandler( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - 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 of chargingStation.connectors.keys()) { - chargingStation.ocppRequestService - .requestHandler< - OCPP16StatusNotificationRequest, - OCPP16StatusNotificationResponse - >( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId: id, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: chargingStation.getConnectorStatus(id)?.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 } - )! + ) + .catch(Constants.EMPTY_FUNCTION) + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED + default: + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED } } @@ -1584,6 +1614,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ) { return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)! const { reservationId, idTag, connectorId } = commandPayload let response: OCPP16ReserveNowResponse try { @@ -1597,8 +1629,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } await removeExpiredReservations(chargingStation) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - switch (chargingStation.getConnectorStatus(connectorId)!.status) { + switch (chargingStation.getConnectorStatus(connectorId)?.status) { case OCPP16ChargePointStatus.Faulted: response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED break @@ -1660,15 +1691,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { try { const { reservationId } = commandPayload const reservation = chargingStation.getReservationBy('reservationId', reservationId) - if (isUndefined(reservation)) { + if (reservation == null) { logger.debug( `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station` ) return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } await chargingStation.removeReservation( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - reservation!, + reservation, ReservationTerminationReason.RESERVATION_CANCELED ) return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED