X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2FOCPPServiceUtils.ts;h=8036079a4b2031c67613bf69ec5744e59cd9ae65;hb=856e8f67312da4acabeff1bd8452f02658a22fdb;hp=dcd7447272a4dccb74c1dc21844141f2353385b2;hpb=e1d9a0f4d6ff1a90048e9a694fd12b7031cc6961;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index dcd74472..8036079a 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -3,19 +3,24 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { DefinedError, ErrorObject, JSONSchemaType } from 'ajv'; +import { isDate } from 'date-fns'; import { OCPP16Constants } from './1.6/OCPP16Constants'; import { OCPP20Constants } from './2.0/OCPP20Constants'; import { OCPPConstants } from './OCPPConstants'; -import { type ChargingStation, ChargingStationConfigurationUtils } from '../../charging-station'; +import { type ChargingStation, getConfigurationKey, getIdTagsFile } from '../../charging-station'; import { BaseError } from '../../exception'; import { + AuthorizationStatus, + type AuthorizeRequest, + type AuthorizeResponse, ChargePointErrorCode, + ChargingStationEvents, + type ConnectorStatus, type ConnectorStatusEnum, ErrorType, FileType, IncomingRequestCommand, - type JsonObject, type JsonType, MessageTrigger, MessageType, @@ -36,6 +41,7 @@ import { isNotEmptyString, logPrefix, logger, + min, } from '../../utils'; export class OCPPServiceUtils { @@ -43,17 +49,19 @@ export class OCPPServiceUtils { // This is intentional } - public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType { - for (const error of errors as DefinedError[]) { - switch (error.keyword) { - case 'type': - return ErrorType.TYPE_CONSTRAINT_VIOLATION; - case 'dependencies': - case 'required': - return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION; - case 'pattern': - case 'format': - return ErrorType.PROPERTY_CONSTRAINT_VIOLATION; + public static ajvErrorsToErrorType(errors: ErrorObject[] | null | undefined): ErrorType { + if (isNotEmptyArray(errors) === true) { + for (const error of errors as DefinedError[]) { + switch (error.keyword) { + case 'type': + return ErrorType.TYPE_CONSTRAINT_VIOLATION; + case 'dependencies': + case 'required': + return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION; + case 'pattern': + case 'format': + return ErrorType.PROPERTY_CONSTRAINT_VIOLATION; + } } } return ErrorType.FORMAT_VIOLATION; @@ -84,9 +92,9 @@ export class OCPPServiceUtils { return true; } else if ( isRequestCommand === true && - chargingStation.stationInfo?.commandsSupport?.outgoingCommands + chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command] ) { - return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false; + return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command]; } logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`); return false; @@ -105,9 +113,9 @@ export class OCPPServiceUtils { return true; } else if ( isIncomingRequestCommand === true && - chargingStation.stationInfo?.commandsSupport?.incomingCommands + chargingStation.stationInfo?.commandsSupport?.incomingCommands?.[command] ) { - return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false; + return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command]; } logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`); return false; @@ -120,8 +128,11 @@ export class OCPPServiceUtils { const isMessageTrigger = Object.values(MessageTrigger).includes(messageTrigger); if (isMessageTrigger === true && !chargingStation.stationInfo?.messageTriggerSupport) { return true; - } else if (isMessageTrigger === true && chargingStation.stationInfo?.messageTriggerSupport) { - return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger] ?? false; + } else if ( + isMessageTrigger === true && + chargingStation.stationInfo?.messageTriggerSupport?.[messageTrigger] + ) { + return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger]; } logger.error( `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`, @@ -144,10 +155,14 @@ export class OCPPServiceUtils { } public static convertDateToISOString(obj: T): void { - for (const key in obj as JsonObject) { - if (obj![key] instanceof Date) { - obj![key] = (obj![key] as Date).toISOString(); + for (const key in obj) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + if (isDate(obj![key])) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + (obj![key] as string) = (obj![key] as Date).toISOString(); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion } else if (obj![key] !== null && typeof obj![key] === 'object') { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion OCPPServiceUtils.convertDateToISOString(obj![key] as T); } } @@ -159,7 +174,7 @@ export class OCPPServiceUtils { status: ConnectorStatusEnum, evseId?: number, ): StatusNotificationRequest { - switch (chargingStation.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16) { + switch (chargingStation.stationInfo?.ocppVersion) { case OCPPVersion.VERSION_16: return { connectorId, @@ -212,6 +227,37 @@ export class OCPPServiceUtils { ); } chargingStation.getConnectorStatus(connectorId)!.status = status; + chargingStation.emit(ChargingStationEvents.connectorStatusChanged, { + connectorId, + ...chargingStation.getConnectorStatus(connectorId), + }); + } + + public static async isIdTagAuthorized( + chargingStation: ChargingStation, + connectorId: number, + idTag: string, + ): Promise { + if ( + !chargingStation.getLocalAuthListEnabled() && + !chargingStation.stationInfo?.remoteAuthorization + ) { + logger.warn( + `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`, + ); + } + if ( + chargingStation.getLocalAuthListEnabled() === true && + OCPPServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag) + ) { + const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!; + connectorStatus.localAuthorizeIdTag = idTag; + connectorStatus.idTagLocalAuthorized = true; + return true; + } else if (chargingStation.stationInfo?.remoteAuthorization) { + return await OCPPServiceUtils.isIdTagRemoteAuthorized(chargingStation, connectorId, idTag); + } + return false; } protected static checkConnectorStatusTransition( @@ -221,7 +267,7 @@ export class OCPPServiceUtils { ): boolean { const fromStatus = chargingStation.getConnectorStatus(connectorId)!.status; let transitionAllowed = false; - switch (chargingStation.stationInfo.ocppVersion) { + switch (chargingStation.stationInfo?.ocppVersion) { case OCPPVersion.VERSION_16: if ( (connectorId === 0 && @@ -254,14 +300,13 @@ export class OCPPServiceUtils { default: throw new BaseError( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo.ocppVersion} not supported`, + `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`, ); } if (transitionAllowed === false) { logger.warn( - `${chargingStation.logPrefix()} OCPP ${ - chargingStation.stationInfo.ocppVersion - } connector id ${connectorId} status transition from '${ + `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo + ?.ocppVersion} connector id ${connectorId} status transition from '${ chargingStation.getConnectorStatus(connectorId)!.status }' to '${status}' is not allowed`, ); @@ -305,7 +350,7 @@ export class OCPPServiceUtils { } if ( measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && - ChargingStationConfigurationUtils.getConfigurationKey( + getConfigurationKey( chargingStation, StandardParametersKey.MeterValuesSampledData, )?.value?.includes(measurand) === false @@ -337,7 +382,7 @@ export class OCPPServiceUtils { phase && sampledValueTemplates[index]?.phase === phase && sampledValueTemplates[index]?.measurand === measurand && - ChargingStationConfigurationUtils.getConfigurationKey( + getConfigurationKey( chargingStation, StandardParametersKey.MeterValuesSampledData, )?.value?.includes(measurand) === true @@ -345,9 +390,9 @@ export class OCPPServiceUtils { return sampledValueTemplates[index]; } else if ( !phase && - !sampledValueTemplates[index].phase && + !sampledValueTemplates[index]?.phase && sampledValueTemplates[index]?.measurand === measurand && - ChargingStationConfigurationUtils.getConfigurationKey( + getConfigurationKey( chargingStation, StandardParametersKey.MeterValuesSampledData, )?.value?.includes(measurand) === true @@ -355,8 +400,8 @@ export class OCPPServiceUtils { return sampledValueTemplates[index]; } else if ( measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && - (!sampledValueTemplates[index].measurand || - sampledValueTemplates[index].measurand === measurand) + (!sampledValueTemplates[index]?.measurand || + sampledValueTemplates[index]?.measurand === measurand) ) { return sampledValueTemplates[index]; } @@ -374,23 +419,50 @@ export class OCPPServiceUtils { protected static getLimitFromSampledValueTemplateCustomValue( value: string, limit: number, - options: { limitationEnabled?: boolean; unitMultiplier?: number } = { - limitationEnabled: true, - unitMultiplier: 1, - }, + options?: { limitationEnabled?: boolean; unitMultiplier?: number; defaultValue?: number }, ): number { options = { ...{ limitationEnabled: true, unitMultiplier: 1, + defaultValue: 0, }, ...options, }; - const parsedInt = parseInt(value); - const numberValue = isNaN(parsedInt) ? Infinity : parsedInt; - return options?.limitationEnabled - ? Math.min(numberValue * options.unitMultiplier!, limit) - : numberValue * options.unitMultiplier!; + const parsedValue = parseInt(value); + if (options?.limitationEnabled) { + return min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, limit); + } + return (!isNaN(parsedValue) ? parsedValue : options.defaultValue!) * options.unitMultiplier!; + } + + private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean { + return ( + chargingStation.hasIdTags() === true && + isNotEmptyString( + chargingStation.idTagsCache + .getIdTags(getIdTagsFile(chargingStation.stationInfo)!) + ?.find((tag) => tag === idTag), + ) + ); + } + + private static async isIdTagRemoteAuthorized( + chargingStation: ChargingStation, + connectorId: number, + idTag: string, + ): Promise { + chargingStation.getConnectorStatus(connectorId)!.authorizeIdTag = idTag; + return ( + ( + await chargingStation.ocppRequestService.requestHandler< + AuthorizeRequest, + AuthorizeResponse + >(chargingStation, RequestCommand.AUTHORIZE, { + idTag, + }) + )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED + ); } private static logPrefix = (