X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16IncomingRequestService.ts;h=5d6172c4f6e273973b083d6f187703fdc1c8aab4;hb=24d15716245469249b57e5aa790147d703d60a4e;hp=f01034a30029b31e40bbcbb8808326d72203a716;hpb=4c8782eeb15a7c41b4ef5a413768abd47615047a;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 f01034a3..5d6172c4 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -1,29 +1,37 @@ -// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. +// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. -import fs from 'node:fs'; -import path from 'node:path'; -import { URL, fileURLToPath } from 'node:url'; +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 { Client, type FTPResponse } from 'basic-ftp'; -import tar from 'tar'; +import type { ValidateFunction } from 'ajv' +import { Client, type FTPResponse } from 'basic-ftp' +import { + type Interval, + addSeconds, + differenceInSeconds, + isDate, + secondsToMilliseconds +} from 'date-fns' +import { maxTime } from 'date-fns/constants' +import { create } from 'tar' -import { OCPP16Constants } from './OCPP16Constants'; -import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; +import { OCPP16Constants } from './OCPP16Constants.js' +import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js' import { type ChargingStation, - ChargingStationConfigurationUtils, - ChargingStationUtils, -} from '../../../charging-station'; -import { OCPPError } from '../../../exception'; + canProceedChargingProfile, + checkChargingStation, + getConfigurationKey, + getConnectorChargingProfiles, + prepareChargingProfileKind, + removeExpiredReservations, + setConfigurationKeyValue +} from '../../../charging-station/index.js' +import { OCPPError } from '../../../exception/index.js' import { - type ChangeAvailabilityRequest, - type ChangeAvailabilityResponse, type ChangeConfigurationRequest, type ChangeConfigurationResponse, - type ClearChargingProfileRequest, - type ClearChargingProfileResponse, - type ConnectorStatus, ErrorType, type GenericResponse, GenericStatus, @@ -32,20 +40,22 @@ import { type GetDiagnosticsRequest, type GetDiagnosticsResponse, type IncomingRequestHandler, - type JsonObject, type JsonType, OCPP16AuthorizationStatus, - type OCPP16AuthorizeRequest, - type OCPP16AuthorizeResponse, OCPP16AvailabilityType, type OCPP16BootNotificationRequest, type OCPP16BootNotificationResponse, + type OCPP16CancelReservationRequest, + type OCPP16ChangeAvailabilityRequest, + type OCPP16ChangeAvailabilityResponse, OCPP16ChargePointErrorCode, OCPP16ChargePointStatus, type OCPP16ChargingProfile, OCPP16ChargingProfilePurposeType, type OCPP16ChargingSchedule, type OCPP16ClearCacheRequest, + type OCPP16ClearChargingProfileRequest, + type OCPP16ClearChargingProfileResponse, type OCPP16DataTransferRequest, type OCPP16DataTransferResponse, OCPP16DataTransferVendorId, @@ -62,6 +72,8 @@ import { OCPP16IncomingRequestCommand, OCPP16MessageTrigger, OCPP16RequestCommand, + type OCPP16ReserveNowRequest, + type OCPP16ReserveNowResponse, OCPP16StandardParametersKey, type OCPP16StartTransactionRequest, type OCPP16StartTransactionResponse, @@ -77,206 +89,337 @@ import { OCPPVersion, type RemoteStartTransactionRequest, type RemoteStopTransactionRequest, + ReservationTerminationReason, type ResetRequest, type SetChargingProfileRequest, type SetChargingProfileResponse, type UnlockConnectorRequest, - type UnlockConnectorResponse, -} from '../../../types'; -import { Constants, Utils, logger } from '../../../utils'; -import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService'; + type UnlockConnectorResponse +} from '../../../types/index.js' +import { + Constants, + convertToDate, + convertToInt, + formatDurationMilliSeconds, + getRandomInteger, + isEmptyArray, + isNotEmptyArray, + isNotEmptyString, + logger, + sleep +} from '../../../utils/index.js' +import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js' -const moduleName = 'OCPP16IncomingRequestService'; +const moduleName = 'OCPP16IncomingRequestService' export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { - protected jsonSchemas: Map>; - private incomingRequestHandlers: Map; + protected jsonSchemasValidateFunction: Map< + OCPP16IncomingRequestCommand, + ValidateFunction + > + + private readonly incomingRequestHandlers: Map< + OCPP16IncomingRequestCommand, + IncomingRequestHandler + > - public constructor() { - // if (new.target?.name === moduleName) { - // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`); + public constructor () { + // if (new.target.name === moduleName) { + // throw new TypeError(`Cannot construct ${new.target.name} instances directly`) // } - super(OCPPVersion.VERSION_16); + super(OCPPVersion.VERSION_16) this.incomingRequestHandlers = new Map([ - [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)], - [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)], - [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)], + [ + 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), + this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, - this.handleRequestChangeConfiguration.bind(this), + this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, - this.handleRequestGetCompositeSchedule.bind(this), + this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, - this.handleRequestSetChargingProfile.bind(this), + this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, - this.handleRequestClearChargingProfile.bind(this), + this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, - this.handleRequestChangeAvailability.bind(this), + this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, - this.handleRequestRemoteStartTransaction.bind(this), + this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, - this.handleRequestRemoteStopTransaction.bind(this), + 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.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)], - [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)], - [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)], - [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)], - ]); - this.jsonSchemas = new Map>([ + [ + OCPP16IncomingRequestCommand.CANCEL_RESERVATION, + this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler + ] + ]) + this.jsonSchemasValidateFunction = 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) ], - ]); - this.validatePayload = this.validatePayload.bind(this) as ( - chargingStation: ChargingStation, - commandName: OCPP16IncomingRequestCommand, - commandPayload: JsonType - ) => boolean; + [ + OCPP16IncomingRequestCommand.RESERVE_NOW, + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ReserveNow.json', + moduleName, + 'constructor' + ) + ) + .bind(this) + ], + [ + OCPP16IncomingRequestCommand.CANCEL_RESERVATION, + this.ajv + .compile( + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/CancelReservation.json', + moduleName, + 'constructor' + ) + ) + .bind(this) + ] + ]) + this.validatePayload = this.validatePayload.bind(this) } - public async incomingRequestHandler( + public async incomingRequestHandler( chargingStation: ChargingStation, messageId: string, commandName: OCPP16IncomingRequestCommand, - commandPayload: JsonType + commandPayload: ReqType ): Promise { - let response: JsonType; + let response: ResType if ( - chargingStation.getOcppStrictCompliance() === true && - chargingStation.inPendingState() === true && + chargingStation.stationInfo?.ocppStrictCompliance === true && + chargingStation.inPendingState() && (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION || commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION) ) { @@ -284,36 +427,37 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request PDU ${JSON.stringify( commandPayload, - null, + undefined, 2 )} while the charging station is in pending state on the central server`, commandName, commandPayload - ); + ) } if ( - chargingStation.isRegistered() === true || - (chargingStation.getOcppStrictCompliance() === false && - chargingStation.inUnknownState() === true) + chargingStation.isRegistered() || + (chargingStation.stationInfo?.ocppStrictCompliance === false && + chargingStation.inUnknownState()) ) { if ( - this.incomingRequestHandlers.has(commandName) === true && - OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true + this.incomingRequestHandlers.has(commandName) && + OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) ) { try { - this.validatePayload(chargingStation, commandName, commandPayload); + this.validatePayload(chargingStation, commandName, commandPayload) // Call the method to build the response - response = await this.incomingRequestHandlers.get(commandName)( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + response = (await this.incomingRequestHandlers.get(commandName)!( chargingStation, commandPayload - ); + )) as ResType } catch (error) { // Log logger.error( `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`, error - ); - throw error; + ) + throw error } } else { // Throw exception @@ -321,24 +465,24 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented to handle request PDU ${JSON.stringify( commandPayload, - null, + undefined, 2 )}`, commandName, commandPayload - ); + ) } } else { throw new OCPPError( ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request PDU ${JSON.stringify( commandPayload, - null, + undefined, 2 )} while the charging station is not registered on the central server.`, commandName, commandPayload - ); + ) } // Send the built response await chargingStation.ocppRequestService.sendResponse( @@ -346,666 +490,642 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { messageId, response, commandName - ); + ) } - private validatePayload( + private validatePayload ( chargingStation: ChargingStation, commandName: OCPP16IncomingRequestCommand, commandPayload: JsonType ): boolean { - if (this.jsonSchemas.has(commandName) === true) { - return this.validateIncomingRequestPayload( - chargingStation, - commandName, - this.jsonSchemas.get(commandName), - commandPayload - ); + if (this.jsonSchemasValidateFunction.has(commandName)) { + return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload) } logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation` - ); - return false; + `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation` + ) + return false } // Simulate charging station restart - private handleRequestReset( + private handleRequestReset ( chargingStation: ChargingStation, commandPayload: ResetRequest ): GenericResponse { - this.runInAsyncScope( - chargingStation.reset.bind(chargingStation) as ( - this: ChargingStation, - ...args: any[] - ) => Promise, - chargingStation, - `${commandPayload.type}Reset` as OCPP16StopTransactionReason - ).catch(Constants.EMPTY_FUNCTION); + const { type } = commandPayload + chargingStation + .reset(`${type}Reset` as OCPP16StopTransactionReason) + .catch(Constants.EMPTY_FUNCTION) logger.info( - `${chargingStation.logPrefix()} ${ - commandPayload.type - } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds( - chargingStation.stationInfo.resetTime + `${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! )}` - ); - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; + ) + return OCPP16Constants.OCPP_RESPONSE_ACCEPTED } - private async handleRequestUnlockConnector( + private async handleRequestUnlockConnector ( chargingStation: ChargingStation, commandPayload: UnlockConnectorRequest ): Promise { - const connectorId = commandPayload.connectorId; - if (chargingStation.hasConnector(connectorId) === false) { + const { connectorId } = commandPayload + if (!chargingStation.hasConnector(connectorId)) { logger.error( - `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId.toString()}` - ); - return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED; + `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}` + ) + return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED } if (connectorId === 0) { - logger.error( - `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}` - ); - return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED; + logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`) + return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED } if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) { const stopResponse = await chargingStation.stopTransactionOnConnector( connectorId, OCPP16StopTransactionReason.UNLOCK_COMMAND - ); + ) if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { - return OCPP16Constants.OCPP_RESPONSE_UNLOCKED; + return OCPP16Constants.OCPP_RESPONSE_UNLOCKED } - return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED; + return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED } await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, OCPP16ChargePointStatus.Available - ); - return OCPP16Constants.OCPP_RESPONSE_UNLOCKED; + ) + return OCPP16Constants.OCPP_RESPONSE_UNLOCKED } - private handleRequestGetConfiguration( + private handleRequestGetConfiguration ( chargingStation: ChargingStation, commandPayload: GetConfigurationRequest ): GetConfigurationResponse { - const configurationKey: OCPPConfigurationKey[] = []; - const unknownKey: string[] = []; - if (Utils.isUndefined(commandPayload.key) === true) { - for (const configuration of chargingStation.ocppConfiguration.configurationKey) { - if (Utils.isUndefined(configuration.visible) === true) { - configuration.visible = true; - } - if (configuration.visible === false) { - continue; + const { key } = commandPayload + const configurationKey: OCPPConfigurationKey[] = [] + const unknownKey: string[] = [] + if (key == null) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + 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 (Utils.isNotEmptyArray(commandPayload.key) === true) { - for (const key of commandPayload.key) { - const keyFound = ChargingStationConfigurationUtils.getConfigurationKey( - chargingStation, - key, - true - ); - if (keyFound) { - if (Utils.isUndefined(keyFound.visible) === true) { - keyFound.visible = true; - } - if (keyFound.visible === false) { - continue; + } else if (isNotEmptyArray(key)) { + for (const k of key) { + const keyFound = getConfigurationKey(chargingStation, k, true) + if (keyFound != null) { + if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) { + continue } configurationKey.push({ key: keyFound.key, readonly: keyFound.readonly, - value: keyFound.value, - }); + value: keyFound.value + }) } else { - unknownKey.push(key); + unknownKey.push(k) } } } return { configurationKey, - unknownKey, - }; + unknownKey + } } - private handleRequestChangeConfiguration( + private handleRequestChangeConfiguration ( chargingStation: ChargingStation, commandPayload: ChangeConfigurationRequest ): ChangeConfigurationResponse { - const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey( - chargingStation, - commandPayload.key, - true - ); - if (!keyToChange) { - return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED; - } else if (keyToChange?.readonly === true) { - return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED; + const { key, value } = commandPayload + const keyToChange = getConfigurationKey(chargingStation, key, true) + if (keyToChange?.readonly === true) { + return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED } else if (keyToChange?.readonly === false) { - let valueChanged = false; - if (keyToChange.value !== commandPayload.value) { - ChargingStationConfigurationUtils.setConfigurationKeyValue( - chargingStation, - commandPayload.key, - commandPayload.value, - true - ); - valueChanged = true; + let valueChanged = false + if (keyToChange.value !== value) { + setConfigurationKeyValue(chargingStation, key, value, true) + valueChanged = true } - let triggerHeartbeatRestart = false; - if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) { - ChargingStationConfigurationUtils.setConfigurationKeyValue( + let triggerHeartbeatRestart = false + if ( + (keyToChange.key as OCPP16StandardParametersKey) === + OCPP16StandardParametersKey.HeartBeatInterval && + valueChanged + ) { + setConfigurationKeyValue( chargingStation, OCPP16StandardParametersKey.HeartbeatInterval, - commandPayload.value - ); - triggerHeartbeatRestart = true; + value + ) + triggerHeartbeatRestart = true } - if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) { - ChargingStationConfigurationUtils.setConfigurationKeyValue( + if ( + (keyToChange.key as OCPP16StandardParametersKey) === + OCPP16StandardParametersKey.HeartbeatInterval && + valueChanged + ) { + setConfigurationKeyValue( chargingStation, OCPP16StandardParametersKey.HeartBeatInterval, - commandPayload.value - ); - triggerHeartbeatRestart = true; + value + ) + triggerHeartbeatRestart = true } if (triggerHeartbeatRestart) { - chargingStation.restartHeartbeat(); + chargingStation.restartHeartbeat() } - if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) { - chargingStation.restartWebSocketPing(); + if ( + (keyToChange.key as OCPP16StandardParametersKey) === + OCPP16StandardParametersKey.WebSocketPingInterval && + valueChanged + ) { + chargingStation.restartWebSocketPing() } - if (keyToChange.reboot) { - return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED; + if (keyToChange.reboot === true) { + return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED } - return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED; + return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED } + return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED } - private handleRequestSetChargingProfile( + private handleRequestSetChargingProfile ( chargingStation: ChargingStation, commandPayload: SetChargingProfileRequest ): SetChargingProfileResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.SmartCharging, OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE - ) === false + ) ) { - return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED; + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED } - if (chargingStation.hasConnector(commandPayload.connectorId) === false) { + const { connectorId, csChargingProfiles } = commandPayload + if (!chargingStation.hasConnector(connectorId)) { logger.error( - `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${ - commandPayload.connectorId - }` - ); - return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED; + `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}` + ) + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED } if ( - commandPayload.csChargingProfiles.chargingProfilePurpose === + csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE && - commandPayload.connectorId !== 0 + connectorId !== 0 ) { - return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED } if ( - commandPayload.csChargingProfiles.chargingProfilePurpose === - OCPP16ChargingProfilePurposeType.TX_PROFILE && - (commandPayload.connectorId === 0 || - chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted === - false) + csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && + connectorId === 0 ) { logger.error( - `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${ - commandPayload.connectorId - } without a started transaction` - ); - return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED; + `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}` + ) + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED } - OCPP16ServiceUtils.setChargingProfile( - chargingStation, - commandPayload.connectorId, - commandPayload.csChargingProfiles - ); + const connectorStatus = chargingStation.getConnectorStatus(connectorId) + if ( + csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && + connectorId > 0 && + connectorStatus?.transactionStarted === false + ) { + logger.error( + `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction` + ) + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED + } + if ( + csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE && + connectorId > 0 && + connectorStatus?.transactionStarted === true && + 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}` + ) + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED + } + OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles) logger.debug( - `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${ - commandPayload.connectorId - }: %j`, - commandPayload.csChargingProfiles - ); - return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED; + `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`, + csChargingProfiles + ) + return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED } - private handleRequestGetCompositeSchedule( + private handleRequestGetCompositeSchedule ( chargingStation: ChargingStation, commandPayload: OCPP16GetCompositeScheduleRequest ): OCPP16GetCompositeScheduleResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.SmartCharging, - OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE - ) === false + OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE + ) ) { - return OCPP16Constants.OCPP_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_RESPONSE_REJECTED } - if (chargingStation.hasConnector(commandPayload.connectorId) === false) { + const { connectorId, duration, chargingRateUnit } = commandPayload + if (!chargingStation.hasConnector(connectorId)) { logger.error( - `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${ - commandPayload.connectorId - }` - ); - return OCPP16Constants.OCPP_RESPONSE_REJECTED; + `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}` + ) + return OCPP16Constants.OCPP_RESPONSE_REJECTED } - if ( - Utils.isEmptyArray( - chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles + if (connectorId === 0) { + logger.error( + `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported` ) + return OCPP16Constants.OCPP_RESPONSE_REJECTED + } + if (chargingRateUnit != null) { + logger.warn( + `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done` + ) + } + const connectorStatus = chargingStation.getConnectorStatus(connectorId) + if ( + isEmptyArray(connectorStatus?.chargingProfiles) && + isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles) ) { - return OCPP16Constants.OCPP_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_RESPONSE_REJECTED + } + const currentDate = new Date() + const compositeScheduleInterval: Interval = { + start: currentDate, + end: addSeconds(currentDate, duration) } - const startDate = new Date(); - const endDate = new Date(startDate.getTime() + commandPayload.duration * 1000); - let compositeSchedule: OCPP16ChargingSchedule; - for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId) - .chargingProfiles) { - // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc. + // Get charging profiles sorted by connector id then stack level + const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles( + chargingStation, + connectorId + ) + let previousCompositeSchedule: OCPP16ChargingSchedule | undefined + let compositeSchedule: OCPP16ChargingSchedule | undefined + for (const chargingProfile of chargingProfiles) { + if (chargingProfile.chargingSchedule.startSchedule == null) { + logger.debug( + `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ + chargingProfile.chargingProfileId + } has no startSchedule defined. Trying to set it to the connector current transaction start date` + ) + // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction + chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart + } + 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` + ) + chargingProfile.chargingSchedule.startSchedule = convertToDate( + chargingProfile.chargingSchedule.startSchedule + ) + } + if (chargingProfile.chargingSchedule.duration == null) { + logger.debug( + `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${ + chargingProfile.chargingProfileId + } has no duration defined and will be set to the maximum time allowed` + ) + // OCPP specifies that if duration is not defined, it should be infinite + chargingProfile.chargingSchedule.duration = differenceInSeconds( + maxTime, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingProfile.chargingSchedule.startSchedule! + ) + } if ( - chargingProfile.chargingSchedule?.startSchedule >= startDate && - chargingProfile.chargingSchedule?.startSchedule <= endDate + !prepareChargingProfileKind( + connectorStatus, + chargingProfile, + compositeScheduleInterval.start, + chargingStation.logPrefix() + ) ) { - compositeSchedule = chargingProfile.chargingSchedule; - break; + continue } + if ( + !canProceedChargingProfile( + chargingProfile, + compositeScheduleInterval.start, + chargingStation.logPrefix() + ) + ) { + continue + } + compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules( + previousCompositeSchedule, + chargingProfile.chargingSchedule, + compositeScheduleInterval + ) + previousCompositeSchedule = compositeSchedule } - return { - status: GenericStatus.Accepted, - scheduleStart: compositeSchedule?.startSchedule, - connectorId: commandPayload.connectorId, - chargingSchedule: compositeSchedule, - }; + if (compositeSchedule != null) { + return { + status: GenericStatus.Accepted, + scheduleStart: compositeSchedule.startSchedule, + connectorId, + chargingSchedule: compositeSchedule + } + } + return OCPP16Constants.OCPP_RESPONSE_REJECTED } - private handleRequestClearChargingProfile( + private handleRequestClearChargingProfile ( chargingStation: ChargingStation, - commandPayload: ClearChargingProfileRequest - ): ClearChargingProfileResponse { + commandPayload: OCPP16ClearChargingProfileRequest + ): OCPP16ClearChargingProfileResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.SmartCharging, OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE - ) === false + ) ) { - return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN; + return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN } - if (chargingStation.hasConnector(commandPayload.connectorId) === false) { + const { connectorId } = commandPayload + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (!chargingStation.hasConnector(connectorId!)) { logger.error( - `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${ - commandPayload.connectorId - }` - ); - return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN; - } - if ( - !Utils.isNullOrUndefined(commandPayload.connectorId) && - Utils.isNotEmptyArray( - chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles + `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}` ) - ) { - chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = []; + return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connectorStatus = chargingStation.getConnectorStatus(connectorId!) + if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) { + connectorStatus.chargingProfiles = [] logger.debug( - `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${ - commandPayload.connectorId - }` - ); - return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED; + `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}` + ) + return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED } - if (Utils.isNullOrUndefined(commandPayload.connectorId)) { - let clearedCP = false; - const clearChargingProfiles = (connectorStatus: ConnectorStatus) => { - if (Utils.isNotEmptyArray(connectorStatus?.chargingProfiles)) { - connectorStatus?.chargingProfiles?.forEach( - (chargingProfile: OCPP16ChargingProfile, index: number) => { - let clearCurrentCP = false; - if (chargingProfile.chargingProfileId === commandPayload.id) { - clearCurrentCP = true; - } - if ( - !commandPayload.chargingProfilePurpose && - chargingProfile.stackLevel === commandPayload.stackLevel - ) { - clearCurrentCP = true; - } - if ( - !chargingProfile.stackLevel && - chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose - ) { - clearCurrentCP = true; - } - if ( - chargingProfile.stackLevel === commandPayload.stackLevel && - chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose - ) { - clearCurrentCP = true; - } - if (clearCurrentCP) { - connectorStatus?.chargingProfiles?.splice(index, 1); - logger.debug( - `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`, - chargingProfile - ); - clearedCP = true; - } - } - ); - } - }; + if (connectorId == null) { + let clearedCP = false if (chargingStation.hasEvses) { for (const evseStatus of chargingStation.evses.values()) { - for (const connectorStatus of evseStatus.connectors.values()) { - clearChargingProfiles(connectorStatus); + for (const status of evseStatus.connectors.values()) { + clearedCP = OCPP16ServiceUtils.clearChargingProfiles( + chargingStation, + commandPayload, + status.chargingProfiles + ) } } } else { - for (const connectorId of chargingStation.connectors.keys()) { - clearChargingProfiles(chargingStation.getConnectorStatus(connectorId)); + for (const id of chargingStation.connectors.keys()) { + clearedCP = OCPP16ServiceUtils.clearChargingProfiles( + chargingStation, + commandPayload, + chargingStation.getConnectorStatus(id)?.chargingProfiles + ) } } if (clearedCP) { - return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED; + return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED } } - return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN; + return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN } - private async handleRequestChangeAvailability( + private async handleRequestChangeAvailability ( chargingStation: ChargingStation, - commandPayload: ChangeAvailabilityRequest - ): Promise { - const connectorId: number = commandPayload.connectorId; - if (chargingStation.hasConnector(connectorId) === false) { + commandPayload: OCPP16ChangeAvailabilityRequest + ): Promise { + const { connectorId, type } = commandPayload + if (!chargingStation.hasConnector(connectorId)) { logger.error( - `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId.toString()}` - ); - return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED; + `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}` + ) + return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED } const chargePointStatus: OCPP16ChargePointStatus = - commandPayload.type === OCPP16AvailabilityType.Operative + type === OCPP16AvailabilityType.Operative ? OCPP16ChargePointStatus.Available - : OCPP16ChargePointStatus.Unavailable; + : OCPP16ChargePointStatus.Unavailable if (connectorId === 0) { - let response: ChangeAvailabilityResponse = - OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED; - const changeAvailability = async (id: number, connectorStatus: ConnectorStatus) => { - if (connectorStatus?.transactionStarted === true) { - response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED; - } - connectorStatus.availability = commandPayload.type; - if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) { - await OCPP16ServiceUtils.sendAndSetConnectorStatus( - chargingStation, - id, - chargePointStatus - ); - } - }; + let response: OCPP16ChangeAvailabilityResponse | undefined if (chargingStation.hasEvses) { for (const evseStatus of chargingStation.evses.values()) { - for (const [id, connectorStatus] of evseStatus.connectors) { - await changeAvailability(id, connectorStatus); - } + response = await OCPP16ServiceUtils.changeAvailability( + chargingStation, + [...evseStatus.connectors.keys()], + chargePointStatus, + type + ) } } else { - for (const id of chargingStation.connectors.keys()) { - await changeAvailability(id, chargingStation.getConnectorStatus(id)); - } + response = await OCPP16ServiceUtils.changeAvailability( + chargingStation, + [...chargingStation.connectors.keys()], + chargePointStatus, + type + ) } - return response; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return response! } else if ( connectorId > 0 && - (chargingStation.isChargingStationAvailable() === true || - (chargingStation.isChargingStationAvailable() === false && - commandPayload.type === OCPP16AvailabilityType.Inoperative)) + (chargingStation.isChargingStationAvailable() || + (!chargingStation.isChargingStationAvailable() && + type === OCPP16AvailabilityType.Inoperative)) ) { if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) { - chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type; - return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.getConnectorStatus(connectorId)!.availability = type + return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED } - chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.getConnectorStatus(connectorId)!.availability = type await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, chargePointStatus - ); - return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED; + ) + return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED } - return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED } - private async handleRequestRemoteStartTransaction( + private async handleRequestRemoteStartTransaction ( chargingStation: ChargingStation, commandPayload: RemoteStartTransactionRequest ): Promise { - const transactionConnectorId = commandPayload.connectorId; - if (chargingStation.hasConnector(transactionConnectorId) === false) { - return this.notifyRemoteStartTransactionRejected( + const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload + if (!chargingStation.hasConnector(transactionConnectorId)) { + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - commandPayload.idTag - ); + idTag + ) } if ( - chargingStation.isChargingStationAvailable() === false || - chargingStation.isConnectorAvailable(transactionConnectorId) === false + !chargingStation.isChargingStationAvailable() || + !chargingStation.isConnectorAvailable(transactionConnectorId) ) { - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - commandPayload.idTag - ); + idTag + ) } - const remoteStartTransactionLogMsg = `${chargingStation.logPrefix()} Transaction remotely STARTED on ${ - chargingStation.stationInfo.chargingStationId - }#${transactionConnectorId.toString()} for idTag '${commandPayload.idTag}'`; + const remoteStartTransactionLogMsg = ` + ${chargingStation.logPrefix()} Transaction remotely STARTED on ${ + chargingStation.stationInfo?.chargingStationId + }#${transactionConnectorId} for idTag '${idTag}'` await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, transactionConnectorId, OCPP16ChargePointStatus.Preparing - ); - const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId); - // Check if authorized - if (chargingStation.getAuthorizeRemoteTxRequests() === true) { - let authorized = false; + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)! + // Authorization check required + if ( + chargingStation.getAuthorizeRemoteTxRequests() && + (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag)) + ) { + // Authorization successful, start transaction if ( - chargingStation.getLocalAuthListEnabled() === true && - chargingStation.hasIdTags() === true && - Utils.isNotEmptyString( - chargingStation.idTagsCache - .getIdTags(ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo)) - ?.find((idTag) => idTag === commandPayload.idTag) - ) - ) { - connectorStatus.localAuthorizeIdTag = commandPayload.idTag; - connectorStatus.idTagLocalAuthorized = true; - authorized = true; - } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) { - connectorStatus.authorizeIdTag = commandPayload.idTag; - const authorizeResponse: OCPP16AuthorizeResponse = - await chargingStation.ocppRequestService.requestHandler< - OCPP16AuthorizeRequest, - OCPP16AuthorizeResponse - >(chargingStation, OCPP16RequestCommand.AUTHORIZE, { - idTag: commandPayload.idTag, - }); - if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { - authorized = true; - } - } else { - logger.warn( - `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled` - ); - } - if (authorized === true) { - // Authorization successful, start transaction - if ( + (chargingProfile != null && this.setRemoteStartTransactionChargingProfile( chargingStation, transactionConnectorId, - commandPayload.chargingProfile - ) === true + chargingProfile + )) || + chargingProfile == null + ) { + connectorStatus.transactionRemoteStarted = true + if ( + ( + await chargingStation.ocppRequestService.requestHandler< + OCPP16StartTransactionRequest, + OCPP16StartTransactionResponse + >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, { + connectorId: transactionConnectorId, + idTag + }) + ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED ) { - connectorStatus.transactionRemoteStarted = true; - if ( - ( - await chargingStation.ocppRequestService.requestHandler< - OCPP16StartTransactionRequest, - OCPP16StartTransactionResponse - >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, { - connectorId: transactionConnectorId, - idTag: commandPayload.idTag, - }) - ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED - ) { - logger.debug(remoteStartTransactionLogMsg); - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; - } - return this.notifyRemoteStartTransactionRejected( - chargingStation, - transactionConnectorId, - commandPayload.idTag - ); + logger.debug(remoteStartTransactionLogMsg) + return OCPP16Constants.OCPP_RESPONSE_ACCEPTED } - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - commandPayload.idTag - ); + idTag + ) } - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - commandPayload.idTag - ); + idTag + ) } // No authorization check required, start transaction if ( - this.setRemoteStartTransactionChargingProfile( - chargingStation, - transactionConnectorId, - commandPayload.chargingProfile - ) === true + (chargingProfile != null && + this.setRemoteStartTransactionChargingProfile( + chargingStation, + transactionConnectorId, + chargingProfile + )) || + chargingProfile == null ) { - connectorStatus.transactionRemoteStarted = true; + connectorStatus.transactionRemoteStarted = true if ( ( await chargingStation.ocppRequestService.requestHandler< - OCPP16StartTransactionRequest, - OCPP16StartTransactionResponse + OCPP16StartTransactionRequest, + OCPP16StartTransactionResponse >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, { connectorId: transactionConnectorId, - idTag: commandPayload.idTag, + idTag }) ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED ) { - logger.debug(remoteStartTransactionLogMsg); - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; + logger.debug(remoteStartTransactionLogMsg) + return OCPP16Constants.OCPP_RESPONSE_ACCEPTED } - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - commandPayload.idTag - ); + idTag + ) } - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - commandPayload.idTag - ); + idTag + ) } - private async notifyRemoteStartTransactionRejected( + private async notifyRemoteStartTransactionRejected ( chargingStation: ChargingStation, connectorId: number, idTag: string ): Promise { - if ( - chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available - ) { + 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.toString()}, idTag '${idTag}', availability '${ - chargingStation.getConnectorStatus(connectorId)?.availability - }', status '${chargingStation.getConnectorStatus(connectorId)?.status}'` - ); - return OCPP16Constants.OCPP_RESPONSE_REJECTED; + `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'` + ) + return OCPP16Constants.OCPP_RESPONSE_REJECTED } - private setRemoteStartTransactionChargingProfile( + private setRemoteStartTransactionChargingProfile ( chargingStation: ChargingStation, connectorId: number, - cp: OCPP16ChargingProfile + chargingProfile: OCPP16ChargingProfile ): boolean { - if (cp && cp.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) { - OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp); + 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`, - cp - ); - return true; - } else if (cp && cp.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE) { - logger.warn( - `${chargingStation.logPrefix()} Not allowed to set ${ - cp.chargingProfilePurpose - } charging profile(s) at remote start transaction` - ); - return false; - } else if (!cp) { - return true; + chargingProfile + ) + return true } + logger.warn( + `${chargingStation.logPrefix()} Not allowed to set ${ + chargingProfile.chargingProfilePurpose + } charging profile(s) at remote start transaction` + ) + return false } - private async handleRequestRemoteStopTransaction( + private async handleRequestRemoteStopTransaction ( chargingStation: ChargingStation, commandPayload: RemoteStopTransactionRequest ): Promise { - const transactionId = commandPayload.transactionId; - const remoteStopTransaction = async (connectorId: number): Promise => { - await OCPP16ServiceUtils.sendAndSetConnectorStatus( - chargingStation, - connectorId, - OCPP16ChargePointStatus.Finishing - ); - const stopResponse = await chargingStation.stopTransactionOnConnector( - connectorId, - OCPP16StopTransactionReason.REMOTE - ); - if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; - } - return OCPP16Constants.OCPP_RESPONSE_REJECTED; - }; + 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 remoteStopTransaction(connectorId); + return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId) } } } @@ -1016,81 +1136,70 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { connectorId > 0 && chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId ) { - return remoteStopTransaction(connectorId); + return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId) } } } logger.warn( - `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id: ${transactionId.toString()}` - ); - return OCPP16Constants.OCPP_RESPONSE_REJECTED; + `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}` + ) + return OCPP16Constants.OCPP_RESPONSE_REJECTED } - private handleRequestUpdateFirmware( + private handleRequestUpdateFirmware ( chargingStation: ChargingStation, commandPayload: OCPP16UpdateFirmwareRequest ): OCPP16UpdateFirmwareResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.FirmwareManagement, OCPP16IncomingRequestCommand.UPDATE_FIRMWARE - ) === false + ) ) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported` - ); - return OCPP16Constants.OCPP_RESPONSE_EMPTY; + ) + return OCPP16Constants.OCPP_RESPONSE_EMPTY } - if ( - !Utils.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; + ) + return OCPP16Constants.OCPP_RESPONSE_EMPTY } - const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate); - const now = Date.now(); - if (retrieveDate?.getTime() <= now) { - this.runInAsyncScope( - this.updateFirmwareSimulation.bind(this) as ( - this: OCPP16IncomingRequestService, - ...args: any[] - ) => Promise, - this, - chargingStation - ).catch(Constants.EMPTY_FUNCTION); + const now = Date.now() + if (retrieveDate.getTime() <= now) { + this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION) } else { setTimeout(() => { - this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION); - }, retrieveDate?.getTime() - now); + this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION) + }, retrieveDate.getTime() - now) } - return OCPP16Constants.OCPP_RESPONSE_EMPTY; + return OCPP16Constants.OCPP_RESPONSE_EMPTY } - private async updateFirmwareSimulation( + private async updateFirmwareSimulation ( chargingStation: ChargingStation, maxDelay = 30, minDelay = 15 ): Promise { - if ( - ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) === - false - ) { - return; + if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) { + return } if (chargingStation.hasEvses) { 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, OCPP16ChargePointStatus.Unavailable - ); + ) } } } @@ -1105,65 +1214,67 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingStation, connectorId, OCPP16ChargePointStatus.Unavailable - ); + ) } } } await chargingStation.ocppRequestService.requestHandler< - OCPP16FirmwareStatusNotificationRequest, - OCPP16FirmwareStatusNotificationResponse + OCPP16FirmwareStatusNotificationRequest, + OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: OCPP16FirmwareStatus.Downloading, - }); - chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading; + status: OCPP16FirmwareStatus.Downloading + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading if ( chargingStation.stationInfo?.firmwareUpgrade?.failureStatus === OCPP16FirmwareStatus.DownloadFailed ) { - await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000); + await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) await chargingStation.ocppRequestService.requestHandler< - OCPP16FirmwareStatusNotificationRequest, - OCPP16FirmwareStatusNotificationResponse + OCPP16FirmwareStatusNotificationRequest, + OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus, - }); + status: chargingStation.stationInfo.firmwareUpgrade.failureStatus + }) chargingStation.stationInfo.firmwareStatus = - chargingStation.stationInfo?.firmwareUpgrade?.failureStatus; - return; + chargingStation.stationInfo.firmwareUpgrade.failureStatus + return } - await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000); + await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) await chargingStation.ocppRequestService.requestHandler< - OCPP16FirmwareStatusNotificationRequest, - OCPP16FirmwareStatusNotificationResponse + OCPP16FirmwareStatusNotificationRequest, + OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: OCPP16FirmwareStatus.Downloaded, - }); - chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded; - let wasTransactionsStarted = false; - let transactionsStarted: boolean; + status: OCPP16FirmwareStatus.Downloaded + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded + let wasTransactionsStarted = false + let transactionsStarted: boolean do { - const runningTransactions = chargingStation.getNumberOfRunningTransactions(); + const runningTransactions = chargingStation.getNumberOfRunningTransactions() if (runningTransactions > 0) { - const waitTime = 15 * 1000; + const waitTime = secondsToMilliseconds(15) logger.debug( - `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${ - waitTime / 1000 - } seconds before continuing firmware update simulation` - ); - await Utils.sleep(waitTime); - transactionsStarted = true; - wasTransactionsStarted = true; + `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds( + waitTime + )} before continuing firmware update simulation` + ) + await sleep(waitTime) + transactionsStarted = true + wasTransactionsStarted = true } else { if (chargingStation.hasEvses) { 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, OCPP16ChargePointStatus.Unavailable - ); + ) } } } @@ -1179,324 +1290,423 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { chargingStation, connectorId, OCPP16ChargePointStatus.Unavailable - ); + ) } } } - transactionsStarted = false; + transactionsStarted = false } - } while (transactionsStarted); + } while (transactionsStarted) !wasTransactionsStarted && - (await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000)); - if ( - ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) === - false - ) { - return; + (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))) + if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) { + return } await chargingStation.ocppRequestService.requestHandler< - OCPP16FirmwareStatusNotificationRequest, - OCPP16FirmwareStatusNotificationResponse + OCPP16FirmwareStatusNotificationRequest, + OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: OCPP16FirmwareStatus.Installing, - }); - chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing; + status: OCPP16FirmwareStatus.Installing + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing if ( chargingStation.stationInfo?.firmwareUpgrade?.failureStatus === OCPP16FirmwareStatus.InstallationFailed ) { - await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000); + await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) await chargingStation.ocppRequestService.requestHandler< - OCPP16FirmwareStatusNotificationRequest, - OCPP16FirmwareStatusNotificationResponse + OCPP16FirmwareStatusNotificationRequest, + OCPP16FirmwareStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus, - }); + status: chargingStation.stationInfo.firmwareUpgrade.failureStatus + }) chargingStation.stationInfo.firmwareStatus = - chargingStation.stationInfo?.firmwareUpgrade?.failureStatus; - return; + chargingStation.stationInfo.firmwareUpgrade.failureStatus + return } if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) { - await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000); - await chargingStation.reset(OCPP16StopTransactionReason.REBOOT); + await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))) + await chargingStation.reset(OCPP16StopTransactionReason.REBOOT) } } - private async handleRequestGetDiagnostics( + private async handleRequestGetDiagnostics ( chargingStation: ChargingStation, commandPayload: GetDiagnosticsRequest ): Promise { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.FirmwareManagement, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS - ) === false + ) ) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported` - ); - return OCPP16Constants.OCPP_RESPONSE_EMPTY; + ) + return OCPP16Constants.OCPP_RESPONSE_EMPTY } - const uri = new URL(commandPayload.location); + const { location } = commandPayload + const uri = new URL(location) if (uri.protocol.startsWith('ftp:')) { - let ftpClient: Client; + let ftpClient: Client | undefined try { - const logFiles = fs - .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../')) - .filter((file) => file.endsWith('.log')) - .map((file) => path.join('./', file)); - const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`; - tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive)); - ftpClient = new Client(); + 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` + create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive)) + ftpClient = new Client() const accessResponse = await ftpClient.access({ host: uri.host, - ...(Utils.isNotEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }), - ...(Utils.isNotEmptyString(uri.username) && { user: uri.username }), - ...(Utils.isNotEmptyString(uri.password) && { password: uri.password }), - }); - let uploadResponse: FTPResponse; + ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }), + ...(isNotEmptyString(uri.username) && { user: uri.username }), + ...(isNotEmptyString(uri.password) && { password: uri.password }) + }) + let uploadResponse: FTPResponse | undefined if (accessResponse.code === 220) { - ftpClient.trackProgress((info) => { + ftpClient.trackProgress(info => { logger.info( - `${chargingStation.logPrefix()} ${ + `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${ info.bytes / 1024 } bytes transferred from diagnostics archive ${info.name}` - ); + ) chargingStation.ocppRequestService .requestHandler< - OCPP16DiagnosticsStatusNotificationRequest, - OCPP16DiagnosticsStatusNotificationResponse - >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { - status: OCPP16DiagnosticsStatus.Uploading, - }) - .catch((error) => { + OCPP16DiagnosticsStatusNotificationRequest, + OCPP16DiagnosticsStatusNotificationResponse + >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { + status: OCPP16DiagnosticsStatus.Uploading + }) + .catch(error => { logger.error( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${ OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION }'`, error - ); - }); - }); + ) + }) + }) uploadResponse = await ftpClient.uploadFrom( - path.join( - path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'), - diagnosticsArchive - ), + join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive), `${uri.pathname}${diagnosticsArchive}` - ); + ) if (uploadResponse.code === 226) { await chargingStation.ocppRequestService.requestHandler< - OCPP16DiagnosticsStatusNotificationRequest, - OCPP16DiagnosticsStatusNotificationResponse + OCPP16DiagnosticsStatusNotificationRequest, + OCPP16DiagnosticsStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { - status: OCPP16DiagnosticsStatus.Uploaded, - }); - if (ftpClient) { - ftpClient.close(); - } - return { fileName: diagnosticsArchive }; + status: OCPP16DiagnosticsStatus.Uploaded + }) + ftpClient.close() + return { fileName: diagnosticsArchive } } throw new OCPPError( ErrorType.GENERIC_ERROR, - `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${ - uploadResponse?.code && `|${uploadResponse?.code.toString()}` - }`, + `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.toString()}${ - uploadResponse?.code && `|${uploadResponse?.code.toString()}` - }`, + `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS - ); + ) } catch (error) { await chargingStation.ocppRequestService.requestHandler< - OCPP16DiagnosticsStatusNotificationRequest, - OCPP16DiagnosticsStatusNotificationResponse + OCPP16DiagnosticsStatusNotificationRequest, + OCPP16DiagnosticsStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { - status: OCPP16DiagnosticsStatus.UploadFailed, - }); - if (ftpClient) { - ftpClient.close(); - } - return this.handleIncomingRequestError( + status: OCPP16DiagnosticsStatus.UploadFailed + }) + ftpClient?.close() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.handleIncomingRequestError( chargingStation, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, error as Error, { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY } - ); + )! } } else { logger.error( `${chargingStation.logPrefix()} Unsupported protocol ${ uri.protocol } to transfer the diagnostic logs archive` - ); + ) await chargingStation.ocppRequestService.requestHandler< - OCPP16DiagnosticsStatusNotificationRequest, - OCPP16DiagnosticsStatusNotificationResponse + OCPP16DiagnosticsStatusNotificationRequest, + OCPP16DiagnosticsStatusNotificationResponse >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, { - status: OCPP16DiagnosticsStatus.UploadFailed, - }); - return OCPP16Constants.OCPP_RESPONSE_EMPTY; + status: OCPP16DiagnosticsStatus.UploadFailed + }) + return OCPP16Constants.OCPP_RESPONSE_EMPTY } } - private handleRequestTriggerMessage( + private handleRequestTriggerMessage ( chargingStation: ChargingStation, commandPayload: OCPP16TriggerMessageRequest ): OCPP16TriggerMessageResponse { + const { requestedMessage, connectorId } = commandPayload if ( !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.RemoteTrigger, OCPP16IncomingRequestCommand.TRIGGER_MESSAGE ) || - !OCPP16ServiceUtils.isMessageTriggerSupported( - chargingStation, - commandPayload.requestedMessage - ) + !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage) ) { - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED; + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED } if ( !OCPP16ServiceUtils.isConnectorIdValid( chargingStation, OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, - commandPayload.connectorId + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + connectorId! ) ) { - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED } try { - switch (commandPayload.requestedMessage) { + switch (requestedMessage) { case OCPP16MessageTrigger.BootNotification: setTimeout(() => { chargingStation.ocppRequestService .requestHandler( - chargingStation, - OCPP16RequestCommand.BOOT_NOTIFICATION, - chargingStation.bootNotificationRequest, - { skipBufferingOnError: true, triggerMessage: true } - ) - .then((response) => { - chargingStation.bootNotificationResponse = response; + 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; + .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, - null, - { - triggerMessage: true, - } - ) - .catch(Constants.EMPTY_FUNCTION); - }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY); - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED; + 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 (!Utils.isNullOrUndefined(commandPayload?.connectorId)) { + 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: commandPayload.connectorId, + connectorId: id, errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: chargingStation.getConnectorStatus(commandPayload.connectorId)?.status, + status: connectorStatus.status }, { - triggerMessage: true, + triggerMessage: true } ) - .catch(Constants.EMPTY_FUNCTION); - } else { - // eslint-disable-next-line no-lonely-if - if (chargingStation.hasEvses) { - for (const evseStatus of chargingStation.evses.values()) { - for (const [connectorId, connectorStatus] of evseStatus.connectors) { - chargingStation.ocppRequestService - .requestHandler< - OCPP16StatusNotificationRequest, - OCPP16StatusNotificationResponse - >( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: connectorStatus.status, - }, - { - triggerMessage: true, - } - ) - .catch(Constants.EMPTY_FUNCTION); - } - } - } else { - for (const connectorId of chargingStation.connectors.keys()) { - chargingStation.ocppRequestService - .requestHandler< - OCPP16StatusNotificationRequest, - OCPP16StatusNotificationResponse - >( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: chargingStation.getConnectorStatus(connectorId)?.status, - }, - { - triggerMessage: true, - } - ) - .catch(Constants.EMPTY_FUNCTION); - } + .catch(Constants.EMPTY_FUNCTION) } } - }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY); - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED; + }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY) + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED default: - return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED; + return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED } } catch (error) { - return this.handleIncomingRequestError( + // 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 } - ); + )! } } - private handleRequestDataTransfer( + private handleRequestDataTransfer ( chargingStation: ChargingStation, commandPayload: OCPP16DataTransferRequest ): OCPP16DataTransferResponse { + const { vendorId } = commandPayload try { - if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) { - return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED; + if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) { + return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED } - return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID; + return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID } catch (error) { - return this.handleIncomingRequestError( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.handleIncomingRequestError( chargingStation, OCPP16IncomingRequestCommand.DATA_TRANSFER, error as Error, { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED } - ); + )! + } + } + + private async handleRequestReserveNow ( + chargingStation: ChargingStation, + commandPayload: OCPP16ReserveNowRequest + ): Promise { + if ( + !OCPP16ServiceUtils.checkFeatureProfile( + chargingStation, + OCPP16SupportedFeatureProfiles.Reservation, + OCPP16IncomingRequestCommand.RESERVE_NOW + ) + ) { + 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 { + if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) { + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED + } + if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) { + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED + } + if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) { + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED + } + await removeExpiredReservations(chargingStation) + switch (chargingStation.getConnectorStatus(connectorId)?.status) { + case OCPP16ChargePointStatus.Faulted: + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED + break + case OCPP16ChargePointStatus.Preparing: + case OCPP16ChargePointStatus.Charging: + case OCPP16ChargePointStatus.SuspendedEV: + case OCPP16ChargePointStatus.SuspendedEVSE: + case OCPP16ChargePointStatus.Finishing: + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED + break + case OCPP16ChargePointStatus.Unavailable: + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE + break + case OCPP16ChargePointStatus.Reserved: + if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) { + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED + break + } + // eslint-disable-next-line no-fallthrough + default: + if (!chargingStation.isConnectorReservable(reservationId, idTag)) { + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED + break + } + await chargingStation.addReservation({ + id: commandPayload.reservationId, + ...commandPayload + }) + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED + break + } + return response + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.handleIncomingRequestError( + chargingStation, + OCPP16IncomingRequestCommand.RESERVE_NOW, + error as Error, + { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED } + )! + } + } + + private async handleRequestCancelReservation ( + chargingStation: ChargingStation, + commandPayload: OCPP16CancelReservationRequest + ): Promise { + if ( + !OCPP16ServiceUtils.checkFeatureProfile( + chargingStation, + OCPP16SupportedFeatureProfiles.Reservation, + OCPP16IncomingRequestCommand.CANCEL_RESERVATION + ) + ) { + return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED + } + try { + const { reservationId } = commandPayload + const reservation = chargingStation.getReservationBy('reservationId', reservationId) + 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( + reservation, + ReservationTerminationReason.RESERVATION_CANCELED + ) + return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.handleIncomingRequestError( + chargingStation, + OCPP16IncomingRequestCommand.CANCEL_RESERVATION, + error as Error, + { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } + )! } } }