X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationUtils.ts;h=ab781fae3cb3382022abb8f61ed7b55f021e9ef0;hb=d972af76b6d7d1d2a099d254eacf45245b5316ac;hp=e2e9821e28c915804c7a9aec4bac79468023e956;hpb=a78ef5ed1052d9cc77435e2dce4aea89442824ee;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index e2e9821e..ab781fae 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -1,14 +1,16 @@ -import crypto from 'node:crypto'; -import path from 'node:path'; +import { createHash, randomBytes } from 'node:crypto'; +import type { EventEmitter } from 'node:events'; +import { basename, dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import chalk from 'chalk'; import moment from 'moment'; -import type { ChargingStation } from './internal'; +import type { ChargingStation } from './ChargingStation'; import { BaseError } from '../exception'; import { AmpereUnits, + AvailabilityType, type BootNotificationRequest, BootReasonEnumType, type ChargingProfile, @@ -17,23 +19,19 @@ import { type ChargingSchedulePeriod, type ChargingStationInfo, type ChargingStationTemplate, + ChargingStationWorkerMessageEvents, + ConnectorPhaseRotation, type ConnectorStatus, + ConnectorStatusEnum, CurrentType, + type EvseTemplate, type OCPP16BootNotificationRequest, type OCPP20BootNotificationRequest, OCPPVersion, RecurrencyKindType, Voltage, } from '../types'; -import { - ACElectricUtils, - Configuration, - Constants, - DCElectricUtils, - Utils, - logger, -} from '../utils'; -import { WorkerProcessType } from '../worker'; +import { ACElectricUtils, Constants, DCElectricUtils, Utils, logger } from '../utils'; const moduleName = 'ChargingStationUtils'; @@ -57,6 +55,19 @@ export class ChargingStationUtils { )}${idSuffix}`; } + public static countReservableConnectors(connectors: Map) { + let reservableConnectors = 0; + for (const [connectorId, connectorStatus] of connectors) { + if (connectorId === 0) { + continue; + } + if (connectorStatus.status === ConnectorStatusEnum.Available) { + ++reservableConnectors; + } + } + return reservableConnectors; + } + public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string { const chargingStationInfo = { chargePointModel: stationTemplate.chargePointModel, @@ -74,8 +85,7 @@ export class ChargingStationUtils { meterType: stationTemplate.meterType, }), }; - return crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) + return createHash(Constants.DEFAULT_HASH_ALGORITHM) .update( `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId( index, @@ -93,6 +103,30 @@ export class ChargingStationUtils { return true; } + public static getPhaseRotationValue( + connectorId: number, + numberOfPhases: number + ): string | undefined { + // AC/DC + if (connectorId === 0 && numberOfPhases === 0) { + return `${connectorId}.${ConnectorPhaseRotation.RST}`; + } else if (connectorId > 0 && numberOfPhases === 0) { + return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`; + // AC + } else if (connectorId > 0 && numberOfPhases === 1) { + return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`; + } else if (connectorId > 0 && numberOfPhases === 3) { + return `${connectorId}.${ConnectorPhaseRotation.RST}`; + } + } + + public static getMaxNumberOfEvses(evses: Record): number { + if (!evses) { + return -1; + } + return Object.keys(evses).length; + } + public static getMaxNumberOfConnectors(connectors: Record): number { if (!connectors) { return -1; @@ -100,60 +134,177 @@ export class ChargingStationUtils { return Object.keys(connectors).length; } - public static checkTemplateMaxConnectors( - templateMaxConnectors: number, - templateFile: string, - logPrefix: string - ): void { - if (templateMaxConnectors === 0) { + public static getBootConnectorStatus( + chargingStation: ChargingStation, + connectorId: number, + connectorStatus: ConnectorStatus + ): ConnectorStatusEnum { + let connectorBootStatus: ConnectorStatusEnum; + if ( + !connectorStatus?.status && + (chargingStation.isChargingStationAvailable() === false || + chargingStation.isConnectorAvailable(connectorId) === false) + ) { + connectorBootStatus = ConnectorStatusEnum.Unavailable; + } else if (!connectorStatus?.status && connectorStatus?.bootStatus) { + // Set boot status in template at startup + connectorBootStatus = connectorStatus?.bootStatus; + } else if (connectorStatus?.status) { + // Set previous status at startup + connectorBootStatus = connectorStatus?.status; + } else { + // Set default status + connectorBootStatus = ConnectorStatusEnum.Available; + } + return connectorBootStatus; + } + + public static checkTemplate( + stationTemplate: ChargingStationTemplate, + logPrefix: string, + templateFile: string + ) { + if (Utils.isNullOrUndefined(stationTemplate)) { + const errorMsg = `Failed to read charging station template file ${templateFile}`; + logger.error(`${logPrefix} ${errorMsg}`); + throw new BaseError(errorMsg); + } + if (Utils.isEmptyObject(stationTemplate)) { + const errorMsg = `Empty charging station information from template file ${templateFile}`; + logger.error(`${logPrefix} ${errorMsg}`); + throw new BaseError(errorMsg); + } + if (Utils.isEmptyObject(stationTemplate.AutomaticTransactionGenerator)) { + stationTemplate.AutomaticTransactionGenerator = Constants.DEFAULT_ATG_CONFIGURATION; logger.warn( - `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration` + `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`, + Constants.DEFAULT_ATG_CONFIGURATION ); - } else if (templateMaxConnectors < 0) { - logger.error( - `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined` + } + if ( + Utils.isNullOrUndefined(stationTemplate.idTagsFile) || + Utils.isEmptyString(stationTemplate.idTagsFile) + ) { + logger.warn( + `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator` ); } } - public static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { - let configuredMaxConnectors: number; - if (Utils.isNotEmptyArray(stationTemplate.numberOfConnectors) === true) { - const numberOfConnectors = stationTemplate.numberOfConnectors as number[]; - configuredMaxConnectors = - numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)]; - } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) { - configuredMaxConnectors = stationTemplate.numberOfConnectors as number; - } else if (stationTemplate.Connectors && !stationTemplate.Evses) { - configuredMaxConnectors = stationTemplate?.Connectors[0] - ? ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors) - 1 - : ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors); - } else if (stationTemplate.Evses && !stationTemplate.Connectors) { - configuredMaxConnectors = 0; - for (const evse in stationTemplate.Evses) { - if (evse === '0') { - continue; - } - configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors( - stationTemplate.Evses[evse].Connectors + public static checkConnectorsConfiguration( + stationTemplate: ChargingStationTemplate, + logPrefix: string, + templateFile: string + ): { + configuredMaxConnectors: number; + templateMaxConnectors: number; + templateMaxAvailableConnectors: number; + } { + const configuredMaxConnectors = + ChargingStationUtils.getConfiguredNumberOfConnectors(stationTemplate); + ChargingStationUtils.checkConfiguredMaxConnectors( + configuredMaxConnectors, + logPrefix, + templateFile + ); + const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors( + stationTemplate.Connectors + ); + ChargingStationUtils.checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile); + const templateMaxAvailableConnectors = stationTemplate?.Connectors[0] + ? templateMaxConnectors - 1 + : templateMaxConnectors; + if ( + configuredMaxConnectors > templateMaxAvailableConnectors && + !stationTemplate?.randomConnectors + ) { + logger.warn( + `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation` + ); + stationTemplate.randomConnectors = true; + } + return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors }; + } + + public static checkStationInfoConnectorStatus( + connectorId: number, + connectorStatus: ConnectorStatus, + logPrefix: string, + templateFile: string + ): void { + if (!Utils.isNullOrUndefined(connectorStatus?.status)) { + logger.warn( + `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it` + ); + delete connectorStatus.status; + } + } + + public static buildConnectorsMap( + connectors: Record, + logPrefix: string, + templateFile: string + ): Map { + const connectorsMap = new Map(); + if (ChargingStationUtils.getMaxNumberOfConnectors(connectors) > 0) { + for (const connector in connectors) { + const connectorStatus = connectors[connector]; + const connectorId = Utils.convertToInt(connector); + ChargingStationUtils.checkStationInfoConnectorStatus( + connectorId, + connectorStatus, + logPrefix, + templateFile ); + connectorsMap.set(connectorId, Utils.cloneObject(connectorStatus)); } + } else { + logger.warn( + `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map` + ); } - return configuredMaxConnectors; + return connectorsMap; } - public static checkConfiguredMaxConnectors( - configuredMaxConnectors: number, - templateFile: string, + public static initializeConnectorsMapStatus( + connectors: Map, logPrefix: string ): void { - if (configuredMaxConnectors <= 0) { - logger.warn( - `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors` - ); + for (const connectorId of connectors.keys()) { + if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) { + logger.warn( + `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${ + connectors.get(connectorId)?.transactionId + }` + ); + } + if (connectorId === 0) { + connectors.get(connectorId).availability = AvailabilityType.Operative; + if (Utils.isUndefined(connectors.get(connectorId)?.chargingProfiles)) { + connectors.get(connectorId).chargingProfiles = []; + } + } else if ( + connectorId > 0 && + Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted) + ) { + ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId)); + } } } + public static resetConnectorStatus(connectorStatus: ConnectorStatus): void { + connectorStatus.idTagLocalAuthorized = false; + connectorStatus.idTagAuthorized = false; + connectorStatus.transactionRemoteStarted = false; + connectorStatus.transactionStarted = false; + delete connectorStatus?.localAuthorizeIdTag; + delete connectorStatus?.authorizeIdTag; + delete connectorStatus?.transactionId; + delete connectorStatus?.transactionIdTag; + connectorStatus.transactionEnergyActiveImportRegisterValue = 0; + delete connectorStatus?.transactionBeginMeterValue; + } + public static createBootNotificationRequest( stationInfo: ChargingStationInfo, bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp @@ -206,20 +357,10 @@ export class ChargingStationUtils { } } - public static workerPoolInUse(): boolean { - return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes( - Configuration.getWorker().processType - ); - } - - public static workerDynamicPoolInUse(): boolean { - return Configuration.getWorker().processType === WorkerProcessType.dynamicPool; - } - public static warnTemplateKeysDeprecation( - templateFile: string, stationTemplate: ChargingStationTemplate, - logPrefix: string + logPrefix: string, + templateFile: string ) { const templateKeys: { key: string; deprecatedKey: string }[] = [ { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' }, @@ -229,8 +370,8 @@ export class ChargingStationUtils { ChargingStationUtils.warnDeprecatedTemplateKey( stationTemplate, templateKey.deprecatedKey, - templateFile, logPrefix, + templateFile, `Use '${templateKey.key}' instead` ); ChargingStationUtils.convertDeprecatedTemplateKey( @@ -244,9 +385,11 @@ export class ChargingStationUtils { public static stationTemplateToStationInfo( stationTemplate: ChargingStationTemplate ): ChargingStationInfo { - stationTemplate = Utils.cloneObject(stationTemplate); + stationTemplate = Utils.cloneObject(stationTemplate); delete stationTemplate.power; delete stationTemplate.powerUnit; + delete stationTemplate?.Connectors; + delete stationTemplate?.Evses; delete stationTemplate.Configuration; delete stationTemplate.AutomaticTransactionGenerator; delete stationTemplate.chargeBoxSerialNumberPrefix; @@ -255,14 +398,6 @@ export class ChargingStationUtils { return stationTemplate as unknown as ChargingStationInfo; } - public static createStationInfoHash(stationInfo: ChargingStationInfo): void { - delete stationInfo.infoHash; - stationInfo.infoHash = crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) - .update(JSON.stringify(stationInfo)) - .digest('hex'); - } - public static createSerialNumber( stationTemplate: ChargingStationTemplate, stationInfo: ChargingStationInfo, @@ -274,27 +409,18 @@ export class ChargingStationUtils { randomSerialNumber: true, } ): void { - params = params ?? {}; - params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true; - params.randomSerialNumber = params?.randomSerialNumber ?? true; + params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params }; const serialNumberSuffix = params?.randomSerialNumber ? ChargingStationUtils.getRandomSerialNumberSuffix({ upperCase: params.randomSerialNumberUpperCase, }) : ''; - stationInfo.chargePointSerialNumber = Utils.isNotEmptyString( - stationTemplate?.chargePointSerialNumberPrefix - ) - ? `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}` - : undefined; - stationInfo.chargeBoxSerialNumber = Utils.isNotEmptyString( - stationTemplate?.chargeBoxSerialNumberPrefix - ) - ? `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}` - : undefined; - stationInfo.meterSerialNumber = Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix) - ? `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}` - : undefined; + Utils.isNotEmptyString(stationTemplate?.chargePointSerialNumberPrefix) && + (stationInfo.chargePointSerialNumber = `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`); + Utils.isNotEmptyString(stationTemplate?.chargeBoxSerialNumberPrefix) && + (stationInfo.chargeBoxSerialNumber = `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`); + Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix) && + (stationInfo.meterSerialNumber = `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`); } public static propagateSerialNumber( @@ -341,15 +467,15 @@ export class ChargingStationUtils { let limit: number, matchingChargingProfile: ChargingProfile; // Get charging profiles for connector and sort by stack level const chargingProfiles = - Utils.cloneObject(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)?.sort( - (a, b) => b.stackLevel - a.stackLevel - ) ?? []; + Utils.cloneObject( + chargingStation.getConnectorStatus(connectorId)?.chargingProfiles + )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? []; // Get profiles on connector 0 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) { chargingProfiles.push( - ...Utils.cloneObject(chargingStation.getConnectorStatus(0).chargingProfiles).sort( - (a, b) => b.stackLevel - a.stackLevel - ) + ...Utils.cloneObject( + chargingStation.getConnectorStatus(0).chargingProfiles + ).sort((a, b) => b.stackLevel - a.stackLevel) ); } if (Utils.isNotEmptyArray(chargingProfiles)) { @@ -397,10 +523,10 @@ export class ChargingStationUtils { public static getDefaultVoltageOut( currentType: CurrentType, - templateFile: string, - logPrefix: string + logPrefix: string, + templateFile: string ): Voltage { - const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`; + const errorMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`; let defaultVoltageOut: number; switch (currentType) { case CurrentType.AC: @@ -410,8 +536,8 @@ export class ChargingStationUtils { defaultVoltageOut = Voltage.VOLTAGE_400; break; default: - logger.error(`${logPrefix} ${errMsg}`); - throw new BaseError(errMsg); + logger.error(`${logPrefix} ${errorMsg}`); + throw new BaseError(errorMsg); } return defaultVoltageOut; } @@ -419,19 +545,101 @@ export class ChargingStationUtils { public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined { return ( stationInfo.idTagsFile && - path.join( - path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'), - 'assets', - path.basename(stationInfo.idTagsFile) - ) + join(dirname(fileURLToPath(import.meta.url)), 'assets', basename(stationInfo.idTagsFile)) ); } + public static waitForChargingStationEvents = async ( + emitter: EventEmitter, + event: ChargingStationWorkerMessageEvents, + eventsToWait: number + ): Promise => { + return new Promise((resolve) => { + let events = 0; + if (eventsToWait === 0) { + resolve(events); + } + emitter.on(event, () => { + ++events; + if (events === eventsToWait) { + resolve(events); + } + }); + }); + }; + + private static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { + let configuredMaxConnectors: number; + if (Utils.isNotEmptyArray(stationTemplate.numberOfConnectors) === true) { + const numberOfConnectors = stationTemplate.numberOfConnectors as number[]; + configuredMaxConnectors = + numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)]; + } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) { + configuredMaxConnectors = stationTemplate.numberOfConnectors as number; + } else if (stationTemplate.Connectors && !stationTemplate.Evses) { + configuredMaxConnectors = stationTemplate?.Connectors[0] + ? ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors) - 1 + : ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors); + } else if (stationTemplate.Evses && !stationTemplate.Connectors) { + configuredMaxConnectors = 0; + for (const evse in stationTemplate.Evses) { + if (evse === '0') { + continue; + } + configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors( + stationTemplate.Evses[evse].Connectors + ); + } + } + return configuredMaxConnectors; + } + + private static checkConfiguredMaxConnectors( + configuredMaxConnectors: number, + logPrefix: string, + templateFile: string + ): void { + if (configuredMaxConnectors <= 0) { + logger.warn( + `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors` + ); + } + } + + private static checkTemplateMaxConnectors( + templateMaxConnectors: number, + logPrefix: string, + templateFile: string + ): void { + if (templateMaxConnectors === 0) { + logger.warn( + `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration` + ); + } else if (templateMaxConnectors < 0) { + logger.error( + `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined` + ); + } + } + + private static initializeConnectorStatus(connectorStatus: ConnectorStatus): void { + connectorStatus.availability = AvailabilityType.Operative; + connectorStatus.idTagLocalAuthorized = false; + connectorStatus.idTagAuthorized = false; + connectorStatus.transactionRemoteStarted = false; + connectorStatus.transactionStarted = false; + connectorStatus.energyActiveImportRegisterValue = 0; + connectorStatus.transactionEnergyActiveImportRegisterValue = 0; + if (Utils.isUndefined(connectorStatus.chargingProfiles)) { + connectorStatus.chargingProfiles = []; + } + } + private static warnDeprecatedTemplateKey( template: ChargingStationTemplate, key: string, - templateFile: string, logPrefix: string, + templateFile: string, logMsgToAppend = '' ): void { if (!Utils.isUndefined(template[key])) { @@ -455,7 +663,7 @@ export class ChargingStationUtils { } /** - * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority) + * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority) * * @param chargingProfiles - * @param logPrefix - @@ -565,9 +773,7 @@ export class ChargingStationUtils { randomBytesLength?: number; upperCase?: boolean; }): string { - const randomSerialNumberSuffix = crypto - .randomBytes(params?.randomBytesLength ?? 16) - .toString('hex'); + const randomSerialNumberSuffix = randomBytes(params?.randomBytesLength ?? 16).toString('hex'); if (params?.upperCase) { return randomSerialNumberSuffix.toUpperCase(); }