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=f5b568dc41eb39ade5eeb28cbfb7fff9459c81c7;hpb=611d5cd2c94317acc7959c930bf962847b059e86;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 f5b568dc..5d6172c4 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -1,31 +1,37 @@ -// 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 { 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 { secondsToMilliseconds } from 'date-fns'; -import { create } 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, + canProceedChargingProfile, checkChargingStation, getConfigurationKey, - setConfigurationKeyValue, -} from '../../../charging-station'; -import { OCPPError } from '../../../exception'; + 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, @@ -34,19 +40,22 @@ import { type GetDiagnosticsRequest, type GetDiagnosticsResponse, type IncomingRequestHandler, - type JsonObject, type JsonType, OCPP16AuthorizationStatus, 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, @@ -85,8 +94,8 @@ import { type SetChargingProfileRequest, type SetChargingProfileResponse, type UnlockConnectorRequest, - type UnlockConnectorResponse, -} from '../../../types'; + type UnlockConnectorResponse +} from '../../../types/index.js' import { Constants, convertToDate, @@ -96,249 +105,321 @@ import { isEmptyArray, isNotEmptyArray, isNotEmptyString, - isNullOrUndefined, - isUndefined, logger, - sleep, -} from '../../../utils'; -import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService'; + 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) as unknown as IncomingRequestHandler, + this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CLEAR_CACHE, - this.handleRequestClearCache.bind(this) as IncomingRequestHandler, + this.handleRequestClearCache.bind(this) as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, - this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.GET_CONFIGURATION, - this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler, + this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, - this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, - this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, - this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, - this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler, + this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, - this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, - this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, - this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, - this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler, + this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, - this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.DATA_TRANSFER, - this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, - this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.RESERVE_NOW, - this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler, + this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler ], [ OCPP16IncomingRequestCommand.CANCEL_RESERVATION, - this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler, - ], - ]); - this.jsonSchemas = new Map>([ + 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) ], [ 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.validatePayload = this.validatePayload.bind(this) as ( - chargingStation: ChargingStation, - commandName: OCPP16IncomingRequestCommand, - commandPayload: JsonType, - ) => boolean; + 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( chargingStation: ChargingStation, messageId: string, commandName: OCPP16IncomingRequestCommand, - commandPayload: ReqType, + commandPayload: ReqType ): Promise { - let response: ResType; + 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) ) { @@ -346,37 +427,37 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request PDU ${JSON.stringify( commandPayload, - null, - 2, + undefined, + 2 )} while the charging station is in pending state on the central server`, commandName, - commandPayload, - ); + 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 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion response = (await this.incomingRequestHandlers.get(commandName)!( chargingStation, - commandPayload, - )) as ResType; + commandPayload + )) as ResType } catch (error) { // Log logger.error( - `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: - Handle incoming request error:`, - error, - ); - throw error; + `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`, + error + ) + throw error } } else { // Throw exception @@ -384,172 +465,156 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented to handle request PDU ${JSON.stringify( commandPayload, - null, - 2, + undefined, + 2 )}`, commandName, - commandPayload, - ); + commandPayload + ) } } else { throw new OCPPError( ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request PDU ${JSON.stringify( commandPayload, - null, - 2, + undefined, + 2 )} while the charging station is not registered on the central server.`, commandName, - commandPayload, - ); + commandPayload + ) } // Send the built response await chargingStation.ocppRequestService.sendResponse( chargingStation, messageId, response, - commandName, - ); + commandName + ) } - private validatePayload( + private validatePayload ( chargingStation: ChargingStation, commandName: OCPP16IncomingRequestCommand, - commandPayload: JsonType, + 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, + commandPayload: ResetRequest ): GenericResponse { - this.runInAsyncScope( - chargingStation.reset.bind(chargingStation) as ( - this: ChargingStation, - ...args: unknown[] - ) => 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 ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime!)}`, - ); - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; + `${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 } - private async handleRequestUnlockConnector( + private async handleRequestUnlockConnector ( chargingStation: ChargingStation, - commandPayload: UnlockConnectorRequest, + 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, - ); + 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; + OCPP16ChargePointStatus.Available + ) + return OCPP16Constants.OCPP_RESPONSE_UNLOCKED } - private handleRequestGetConfiguration( + private handleRequestGetConfiguration ( chargingStation: ChargingStation, - commandPayload: GetConfigurationRequest, + commandPayload: GetConfigurationRequest ): GetConfigurationResponse { - const configurationKey: OCPPConfigurationKey[] = []; - const unknownKey: string[] = []; - if (isUndefined(commandPayload.key) === true) { - for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) { - if (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 (isNotEmptyArray(commandPayload.key) === true) { - for (const key of commandPayload.key!) { - const keyFound = getConfigurationKey(chargingStation, key, true); - if (keyFound) { - if (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, + commandPayload: ChangeConfigurationRequest ): ChangeConfigurationResponse { - const keyToChange = getConfigurationKey(chargingStation, commandPayload.key, true); + const { key, value } = commandPayload + const keyToChange = getConfigurationKey(chargingStation, key, true) if (keyToChange?.readonly === true) { - return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED } else if (keyToChange?.readonly === false) { - let valueChanged = false; - if (keyToChange.value !== commandPayload.value) { - 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; + let triggerHeartbeatRestart = false if ( (keyToChange.key as OCPP16StandardParametersKey) === OCPP16StandardParametersKey.HeartBeatInterval && @@ -558,9 +623,9 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { setConfigurationKeyValue( chargingStation, OCPP16StandardParametersKey.HeartbeatInterval, - commandPayload.value, - ); - triggerHeartbeatRestart = true; + value + ) + triggerHeartbeatRestart = true } if ( (keyToChange.key as OCPP16StandardParametersKey) === @@ -570,475 +635,497 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { setConfigurationKeyValue( chargingStation, OCPP16StandardParametersKey.HeartBeatInterval, - commandPayload.value, - ); - triggerHeartbeatRestart = true; + value + ) + triggerHeartbeatRestart = true } if (triggerHeartbeatRestart) { - chargingStation.restartHeartbeat(); + chargingStation.restartHeartbeat() } if ( (keyToChange.key as OCPP16StandardParametersKey) === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged ) { - chargingStation.restartWebSocketPing(); + 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; + return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED } - private handleRequestSetChargingProfile( + private handleRequestSetChargingProfile ( chargingStation: ChargingStation, - commandPayload: SetChargingProfileRequest, + commandPayload: SetChargingProfileRequest ): SetChargingProfileResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.SmartCharging, - OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, - ) === false + OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE + ) ) { - 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, + commandPayload: OCPP16GetCompositeScheduleRequest ): OCPP16GetCompositeScheduleResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.SmartCharging, - OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, - ) === 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 (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(chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles) + isEmptyArray(connectorStatus?.chargingProfiles) && + isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles) ) { - return OCPP16Constants.OCPP_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_RESPONSE_REJECTED } - const startDate = new Date(); - const endDate = new Date(startDate.getTime() + secondsToMilliseconds(commandPayload.duration)); - let compositeSchedule: OCPP16ChargingSchedule | undefined; - 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. + const currentDate = new Date() + const compositeScheduleInterval: Interval = { + start: currentDate, + end: addSeconds(currentDate, duration) + } + // 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 ( + !prepareChargingProfileKind( + connectorStatus, + chargingProfile, + compositeScheduleInterval.start, + chargingStation.logPrefix() + ) + ) { + continue + } if ( - chargingProfile.chargingSchedule.startSchedule! >= startDate && - chargingProfile.chargingSchedule.startSchedule! <= endDate + !canProceedChargingProfile( + chargingProfile, + compositeScheduleInterval.start, + chargingStation.logPrefix() + ) ) { - compositeSchedule = chargingProfile.chargingSchedule; - break; + 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 + OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE + ) ) { - 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 ( - !isNullOrUndefined(commandPayload.connectorId) && - 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 (isNullOrUndefined(commandPayload.connectorId)) { - let clearedCP = false; + if (connectorId == null) { + let clearedCP = false if (chargingStation.hasEvses) { for (const evseStatus of chargingStation.evses.values()) { - for (const connectorStatus of evseStatus.connectors.values()) { + for (const status of evseStatus.connectors.values()) { clearedCP = OCPP16ServiceUtils.clearChargingProfiles( chargingStation, commandPayload, - connectorStatus.chargingProfiles, - ); + status.chargingProfiles + ) } } } else { - for (const connectorId of chargingStation.connectors.keys()) { + for (const id of chargingStation.connectors.keys()) { clearedCP = OCPP16ServiceUtils.clearChargingProfiles( chargingStation, commandPayload, - chargingStation.getConnectorStatus(connectorId)?.chargingProfiles, - ); + 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; + chargePointStatus + ) + 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, + commandPayload: RemoteStartTransactionRequest ): Promise { - const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload; - if ( - (chargingStation.getConnectorStatus(transactionConnectorId)?.status === - OCPP16ChargePointStatus.Reserved && - chargingStation.getReservationBy('connectorId', transactionConnectorId)?.idTag !== idTag) || - (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved && - chargingStation.getReservationBy('connectorId', 0)?.idTag !== idTag) - ) { - return OCPP16Constants.OCPP_RESPONSE_REJECTED; - } - if (chargingStation.hasConnector(transactionConnectorId) === false) { - return this.notifyRemoteStartTransactionRejected( + const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload + if (!chargingStation.hasConnector(transactionConnectorId)) { + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - idTag, - ); + idTag + ) } if ( !chargingStation.isChargingStationAvailable() || !chargingStation.isConnectorAvailable(transactionConnectorId) ) { - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - idTag, - ); + idTag + ) } const remoteStartTransactionLogMsg = ` ${chargingStation.logPrefix()} Transaction remotely STARTED on ${ - chargingStation.stationInfo.chargingStationId - }#${transactionConnectorId.toString()} for idTag '${idTag}'`; + chargingStation.stationInfo?.chargingStationId + }#${transactionConnectorId} for idTag '${idTag}'` await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, transactionConnectorId, - OCPP16ChargePointStatus.Preparing, - ); - const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!; - if ( - chargingStation.getAuthorizeRemoteTxRequests() && - !chargingStation.getLocalAuthListEnabled() && - !chargingStation.getMustAuthorizeAtRemoteStart() - ) { - logger.warn( - `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction - but local authorization or must authorize at remote start isn't enabled`, - ); - } + OCPP16ChargePointStatus.Preparing + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)! // Authorization check required if ( - chargingStation.getAuthorizeRemoteTxRequests() === true && - chargingStation.getMustAuthorizeAtRemoteStart() === true && + chargingStation.getAuthorizeRemoteTxRequests() && (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag)) ) { // Authorization successful, start transaction if ( - this.setRemoteStartTransactionChargingProfile( - chargingStation, - transactionConnectorId, - 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, - reservationId: chargingStation.getReservationBy( - 'connectorId', - chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved - ? 0 - : transactionConnectorId, - )!, + 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, - idTag, - ); + idTag + ) } - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - idTag, - ); + idTag + ) } // No authorization check required, start transaction if ( - this.setRemoteStartTransactionChargingProfile( - chargingStation, - transactionConnectorId, - 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, - reservationId: chargingStation.getReservationBy( - 'connectorId', - chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved - ? 0 - : transactionConnectorId, - )!, + 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, - idTag, - ); + idTag + ) } - return this.notifyRemoteStartTransactionRejected( + return await this.notifyRemoteStartTransactionRejected( chargingStation, transactionConnectorId, - idTag, - ); + idTag + ) } - private async notifyRemoteStartTransactionRejected( + private async notifyRemoteStartTransactionRejected ( chargingStation: ChargingStation, connectorId: number, - idTag: string, + 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, - ); + 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, - chargingProfile: OCPP16ChargingProfile, + chargingProfile: OCPP16ChargingProfile ): boolean { - if ( - chargingProfile && - chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE - ) { - OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile); + 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`, - chargingProfile, - ); - return true; - } else if ( - chargingProfile && - chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE - ) { - logger.warn( - `${chargingStation.logPrefix()} Not allowed to set ${ - chargingProfile.chargingProfilePurpose - } charging profile(s) at remote start transaction`, - ); - return false; + `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`, + chargingProfile + ) + return true } - 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, + 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) } } } @@ -1049,91 +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, + commandPayload: OCPP16UpdateFirmwareRequest ): OCPP16UpdateFirmwareResponse { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.FirmwareManagement, - OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, - ) === false + OCPP16IncomingRequestCommand.UPDATE_FIRMWARE + ) ) { logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: - Cannot simulate firmware update: feature profile not supported`, - ); - return OCPP16Constants.OCPP_RESPONSE_EMPTY; + `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported` + ) + return OCPP16Constants.OCPP_RESPONSE_EMPTY } - 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; + `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress` + ) + return OCPP16Constants.OCPP_RESPONSE_EMPTY } - const retrieveDate = convertToDate(commandPayload.retrieveDate)!; - const now = Date.now(); - if (retrieveDate?.getTime() <= now) { - this.runInAsyncScope( - this.updateFirmwareSimulation.bind(this) as ( - this: OCPP16IncomingRequestService, - ...args: unknown[] - ) => 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.runInAsyncScope( - this.updateFirmwareSimulation.bind(this) as ( - this: OCPP16IncomingRequestService, - ...args: unknown[] - ) => Promise, - this, - chargingStation, - ).catch(Constants.EMPTY_FUNCTION); - }, - retrieveDate?.getTime() - now, - ); + setTimeout(() => { + 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, + minDelay = 15 ): Promise { - if (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, - ); + OCPP16ChargePointStatus.Unavailable + ) } } } @@ -1147,67 +1213,68 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, - OCPP16ChargePointStatus.Unavailable, - ); + 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 sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))); + 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 sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))); + 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 = secondsToMilliseconds(15); + const waitTime = secondsToMilliseconds(15) logger.debug( - `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: - ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds( - waitTime, - )} before continuing firmware update simulation`, - ); - await 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, - ); + OCPP16ChargePointStatus.Unavailable + ) } } } @@ -1222,425 +1289,424 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { await OCPP16ServiceUtils.sendAndSetConnectorStatus( chargingStation, connectorId, - OCPP16ChargePointStatus.Unavailable, - ); + OCPP16ChargePointStatus.Unavailable + ) } } } - transactionsStarted = false; + transactionsStarted = false } - } while (transactionsStarted); + } while (transactionsStarted) !wasTransactionsStarted && - (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))); - if (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 sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))); + 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 sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))); - 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, + commandPayload: GetDiagnosticsRequest ): Promise { if ( - OCPP16ServiceUtils.checkFeatureProfile( + !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.FirmwareManagement, - OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, - ) === false + OCPP16IncomingRequestCommand.GET_DIAGNOSTICS + ) ) { logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: - Cannot get diagnostics: feature profile not supported`, - ); - return OCPP16Constants.OCPP_RESPONSE_EMPTY; + `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported` + ) + 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 | undefined; + 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`; - create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive)); - ftpClient = new Client(); + .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, ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }), ...(isNotEmptyString(uri.username) && { user: uri.username }), - ...(isNotEmptyString(uri.password) && { password: uri.password }), - }); - let uploadResponse: FTPResponse | undefined; + ...(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}`, - ); + } 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, - ); - }); - }); + `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${ + OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION + }'`, + error + ) + }) + }) uploadResponse = await ftpClient.uploadFrom( join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive), - `${uri.pathname}${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()}` - }`, - OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, - ); + `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()}` - }`, - OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, - ); + `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(); - } + 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 }, - )!; + { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY } + )! } } else { logger.error( `${chargingStation.logPrefix()} Unsupported protocol ${ uri.protocol - } to transfer the diagnostic logs archive`, - ); + } 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, + commandPayload: OCPP16TriggerMessageRequest ): OCPP16TriggerMessageResponse { + const { requestedMessage, connectorId } = commandPayload if ( !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.RemoteTrigger, - OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, + 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 (!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, - }, - ) - .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); + triggerMessage: true } - } - } 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) { + // 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 }, - )!; + { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED } + )! } } - private handleRequestDataTransfer( + private handleRequestDataTransfer ( chargingStation: ChargingStation, - commandPayload: OCPP16DataTransferRequest, + 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) { + // 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 }, - )!; + { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED } + )! } } - private async handleRequestReserveNow( + private async handleRequestReserveNow ( chargingStation: ChargingStation, - commandPayload: OCPP16ReserveNowRequest, + commandPayload: OCPP16ReserveNowRequest ): Promise { if ( !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.Reservation, - OCPP16IncomingRequestCommand.RESERVE_NOW, + OCPP16IncomingRequestCommand.RESERVE_NOW ) ) { - return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } - const { reservationId, idTag, connectorId } = commandPayload; - let response: OCPP16ReserveNowResponse; + // 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; + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) { - return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) { - return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } - switch (chargingStation.getConnectorStatus(connectorId)!.status) { + await removeExpiredReservations(chargingStation) + switch (chargingStation.getConnectorStatus(connectorId)?.status) { case OCPP16ChargePointStatus.Faulted: - response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED; - break; + 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; + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED + break case OCPP16ChargePointStatus.Unavailable: - response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE; - break; + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE + break case OCPP16ChargePointStatus.Reserved: if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) { - response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED; - break; + 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; + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED + break } await chargingStation.addReservation({ id: commandPayload.reservationId, - ...commandPayload, - }); - response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED; - break; + ...commandPayload + }) + response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED + break } - return response; + return response } catch (error) { - chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available; + // 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 }, - )!; + { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED } + )! } } - private async handleRequestCancelReservation( + private async handleRequestCancelReservation ( chargingStation: ChargingStation, - commandPayload: OCPP16CancelReservationRequest, + commandPayload: OCPP16CancelReservationRequest ): Promise { if ( !OCPP16ServiceUtils.checkFeatureProfile( chargingStation, OCPP16SupportedFeatureProfiles.Reservation, - OCPP16IncomingRequestCommand.CANCEL_RESERVATION, + OCPP16IncomingRequestCommand.CANCEL_RESERVATION ) ) { - return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED; + return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } try { - const { reservationId } = commandPayload; - const reservation = chargingStation.getReservationBy('reservationId', reservationId); - if (isUndefined(reservation)) { - logger.error( - `${chargingStation.logPrefix()} Reservation with ID ${reservationId} - does not exist on charging station`, - ); - return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED; + 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; + 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 }, - )!; + { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } + )! } } }