X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FHelpers.ts;h=15ba379bf50ff28fc28fcb3ae3ab7c93eadd5b81;hb=aa5f2ea2958ef34e8d34e20edbe8486b8911d82b;hp=c2694498ecec09789f5fa02f6bbfd62cabc4d5d4;hpb=a223d9be48ad8828e6aef060dd3c45d4f99ea9a9;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index c2694498..15ba379b 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -1,18 +1,18 @@ import { createHash, randomBytes } from 'node:crypto' import type { EventEmitter } from 'node:events' -import { basename, dirname, join } from 'node:path' +import { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path' import { env } from 'node:process' import { fileURLToPath } from 'node:url' import chalk from 'chalk' import { - type Interval, addDays, addSeconds, addWeeks, differenceInDays, differenceInSeconds, differenceInWeeks, + type Interval, isAfter, isBefore, isDate, @@ -21,9 +21,8 @@ import { toDate } from 'date-fns' import { maxTime } from 'date-fns/constants' +import { isEmpty } from 'rambda' -import type { ChargingStation } from './ChargingStation.js' -import { getConfigurationKey } from './ConfigurationKeyUtils.js' import { BaseError } from '../exception/index.js' import { AmpereUnits, @@ -36,6 +35,7 @@ import { type ChargingSchedulePeriod, type ChargingStationConfiguration, type ChargingStationInfo, + type ChargingStationOptions, type ChargingStationTemplate, type ChargingStationWorkerMessageEvents, ConnectorPhaseRotation, @@ -55,23 +55,34 @@ import { } from '../types/index.js' import { ACElectricUtils, + clone, Constants, - DCElectricUtils, - cloneObject, convertToDate, convertToInt, + DCElectricUtils, isArraySorted, - isEmptyObject, - isEmptyString, isNotEmptyArray, isNotEmptyString, - isValidTime, + isValidDate, logger, secureRandom } from '../utils/index.js' +import type { ChargingStation } from './ChargingStation.js' +import { getConfigurationKey } from './ConfigurationKeyUtils.js' const moduleName = 'Helpers' +export const buildTemplateName = (templateFile: string): string => { + if (isAbsolute(templateFile)) { + templateFile = relative( + resolve(join(dirname(fileURLToPath(import.meta.url)), 'assets', 'station-templates')), + templateFile + ) + } + const templateFileParsedPath = parse(templateFile) + return join(templateFileParsedPath.dir, templateFileParsedPath.name) +} + export const getChargingStationId = ( index: number, stationTemplate: ChargingStationTemplate | undefined @@ -145,16 +156,16 @@ export const getHashId = (index: number, stationTemplate: ChargingStationTemplat const chargingStationInfo = { chargePointModel: stationTemplate.chargePointModel, chargePointVendor: stationTemplate.chargePointVendor, - ...(stationTemplate.chargeBoxSerialNumberPrefix !== undefined && { + ...(stationTemplate.chargeBoxSerialNumberPrefix != null && { chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix }), - ...(stationTemplate.chargePointSerialNumberPrefix !== undefined && { + ...(stationTemplate.chargePointSerialNumberPrefix != null && { chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix }), - ...(stationTemplate.meterSerialNumberPrefix !== undefined && { + ...(stationTemplate.meterSerialNumberPrefix != null && { meterSerialNumber: stationTemplate.meterSerialNumberPrefix }), - ...(stationTemplate.meterType !== undefined && { + ...(stationTemplate.meterType != null && { meterType: stationTemplate.meterType }) } @@ -242,20 +253,12 @@ export const checkTemplate = ( logger.error(`${logPrefix} ${errorMsg}`) throw new BaseError(errorMsg) } - if (isEmptyObject(stationTemplate)) { + if (isEmpty(stationTemplate)) { const errorMsg = `Empty charging station information from template file ${templateFile}` logger.error(`${logPrefix} ${errorMsg}`) throw new BaseError(errorMsg) } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (isEmptyObject(stationTemplate.AutomaticTransactionGenerator!)) { - stationTemplate.AutomaticTransactionGenerator = Constants.DEFAULT_ATG_CONFIGURATION - logger.warn( - `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`, - Constants.DEFAULT_ATG_CONFIGURATION - ) - } - if (stationTemplate.idTagsFile == null || isEmptyString(stationTemplate.idTagsFile)) { + if (stationTemplate.idTagsFile == null || isEmpty(stationTemplate.idTagsFile)) { logger.warn( `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator` ) @@ -272,7 +275,7 @@ export const checkConfiguration = ( logger.error(`${logPrefix} ${errorMsg}`) throw new BaseError(errorMsg) } - if (isEmptyObject(stationConfiguration)) { + if (isEmpty(stationConfiguration)) { const errorMsg = `Empty charging station configuration from file ${configurationFile}` logger.error(`${logPrefix} ${errorMsg}`) throw new BaseError(errorMsg) @@ -303,7 +306,11 @@ export const checkConnectorsConfiguration = ( ) stationTemplate.randomConnectors = true } - return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors } + return { + configuredMaxConnectors, + templateMaxConnectors, + templateMaxAvailableConnectors + } } export const checkStationInfoConnectorStatus = ( @@ -331,7 +338,7 @@ export const buildConnectorsMap = ( const connectorStatus = connectors[connector] const connectorId = convertToInt(connector) checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile) - connectorsMap.set(connectorId, cloneObject(connectorStatus)) + connectorsMap.set(connectorId, clone(connectorStatus)) } } else { logger.warn( @@ -341,6 +348,37 @@ export const buildConnectorsMap = ( return connectorsMap } +export const setChargingStationOptions = ( + stationInfo: ChargingStationInfo, + options?: ChargingStationOptions +): ChargingStationInfo => { + if (options?.supervisionUrls != null) { + stationInfo.supervisionUrls = options.supervisionUrls + } + if (options?.persistentConfiguration != null) { + stationInfo.stationInfoPersistentConfiguration = options.persistentConfiguration + stationInfo.ocppPersistentConfiguration = options.persistentConfiguration + stationInfo.automaticTransactionGeneratorPersistentConfiguration = + options.persistentConfiguration + } + if (options?.autoStart != null) { + stationInfo.autoStart = options.autoStart + } + if (options?.autoRegister != null) { + stationInfo.autoRegister = options.autoRegister + } + if (options?.enableStatistics != null) { + stationInfo.enableStatistics = options.enableStatistics + } + if (options?.ocppStrictCompliance != null) { + stationInfo.ocppStrictCompliance = options.ocppStrictCompliance + } + if (options?.stopTransactionsOnStopped != null) { + stationInfo.stopTransactionsOnStopped = options.stopTransactionsOnStopped + } + return stationInfo +} + export const initializeConnectorsMapStatus = ( connectors: Map, logPrefix: string @@ -373,8 +411,8 @@ export const resetConnectorStatus = (connectorStatus: ConnectorStatus | undefine } connectorStatus.chargingProfiles = connectorStatus.transactionId != null && isNotEmptyArray(connectorStatus.chargingProfiles) - ? connectorStatus.chargingProfiles?.filter( - (chargingProfile) => chargingProfile.transactionId !== connectorStatus.transactionId + ? connectorStatus.chargingProfiles.filter( + chargingProfile => chargingProfile.transactionId !== connectorStatus.transactionId ) : [] connectorStatus.idTagLocalAuthorized = false @@ -400,21 +438,21 @@ export const createBootNotificationRequest = ( return { chargePointModel: stationInfo.chargePointModel, chargePointVendor: stationInfo.chargePointVendor, - ...(stationInfo.chargeBoxSerialNumber !== undefined && { + ...(stationInfo.chargeBoxSerialNumber != null && { chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber }), - ...(stationInfo.chargePointSerialNumber !== undefined && { + ...(stationInfo.chargePointSerialNumber != null && { chargePointSerialNumber: stationInfo.chargePointSerialNumber }), - ...(stationInfo.firmwareVersion !== undefined && { + ...(stationInfo.firmwareVersion != null && { firmwareVersion: stationInfo.firmwareVersion }), - ...(stationInfo.iccid !== undefined && { iccid: stationInfo.iccid }), - ...(stationInfo.imsi !== undefined && { imsi: stationInfo.imsi }), - ...(stationInfo.meterSerialNumber !== undefined && { + ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }), + ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }), + ...(stationInfo.meterSerialNumber != null && { meterSerialNumber: stationInfo.meterSerialNumber }), - ...(stationInfo.meterType !== undefined && { + ...(stationInfo.meterType != null && { meterType: stationInfo.meterType }) } satisfies OCPP16BootNotificationRequest @@ -425,16 +463,16 @@ export const createBootNotificationRequest = ( chargingStation: { model: stationInfo.chargePointModel, vendorName: stationInfo.chargePointVendor, - ...(stationInfo.firmwareVersion !== undefined && { + ...(stationInfo.firmwareVersion != null && { firmwareVersion: stationInfo.firmwareVersion }), - ...(stationInfo.chargeBoxSerialNumber !== undefined && { + ...(stationInfo.chargeBoxSerialNumber != null && { serialNumber: stationInfo.chargeBoxSerialNumber }), - ...((stationInfo.iccid !== undefined || stationInfo.imsi !== undefined) && { + ...((stationInfo.iccid != null || stationInfo.imsi != null) && { modem: { - ...(stationInfo.iccid !== undefined && { iccid: stationInfo.iccid }), - ...(stationInfo.imsi !== undefined && { imsi: stationInfo.imsi }) + ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }), + ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }) } }) } @@ -459,7 +497,7 @@ export const warnTemplateKeysDeprecation = ( templateKey.deprecatedKey, logPrefix, templateFile, - templateKey.key !== undefined ? `Use '${templateKey.key}' instead` : undefined + templateKey.key != null ? `Use '${templateKey.key}' instead` : undefined ) convertDeprecatedTemplateKey(stationTemplate, templateKey.deprecatedKey, templateKey.key) } @@ -468,13 +506,14 @@ export const warnTemplateKeysDeprecation = ( export const stationTemplateToStationInfo = ( stationTemplate: ChargingStationTemplate ): ChargingStationInfo => { - stationTemplate = cloneObject(stationTemplate) + stationTemplate = clone(stationTemplate) delete stationTemplate.power delete stationTemplate.powerUnit delete stationTemplate.Connectors delete stationTemplate.Evses delete stationTemplate.Configuration delete stationTemplate.AutomaticTransactionGenerator + delete stationTemplate.numberOfConnectors delete stationTemplate.chargeBoxSerialNumberPrefix delete stationTemplate.chargePointSerialNumberPrefix delete stationTemplate.meterSerialNumberPrefix @@ -489,7 +528,10 @@ export const createSerialNumber = ( randomSerialNumber?: boolean } ): void => { - params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params } + params = { + ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, + ...params + } const serialNumberSuffix = params.randomSerialNumber === true ? getRandomSerialNumberSuffix({ @@ -566,7 +608,7 @@ export const getConnectorChargingProfiles = ( chargingStation: ChargingStation, connectorId: number ): ChargingProfile[] => { - return cloneObject( + return clone( (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []) .sort((a, b) => b.stackLevel - a.stackLevel) .concat( @@ -662,7 +704,7 @@ export const waitChargingStationEvents = async ( event: ChargingStationWorkerMessageEvents, eventsToWait: number ): Promise => { - return await new Promise((resolve) => { + return await new Promise(resolve => { let events = 0 if (eventsToWait === 0) { resolve(events) @@ -680,11 +722,11 @@ export const waitChargingStationEvents = async ( const getConfiguredMaxNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => { let configuredMaxNumberOfConnectors = 0 if (isNotEmptyArray(stationTemplate.numberOfConnectors)) { - const numberOfConnectors = stationTemplate.numberOfConnectors as number[] + const numberOfConnectors = stationTemplate.numberOfConnectors configuredMaxNumberOfConnectors = numberOfConnectors[Math.floor(secureRandom() * numberOfConnectors.length)] } else if (stationTemplate.numberOfConnectors != null) { - configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors as number + configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors } else if (stationTemplate.Connectors != null && stationTemplate.Evses == null) { configuredMaxNumberOfConnectors = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -752,7 +794,7 @@ const warnDeprecatedTemplateKey = ( templateFile: string, logMsgToAppend = '' ): void => { - if (template[key as keyof ChargingStationTemplate] !== undefined) { + if (template[key as keyof ChargingStationTemplate] != null) { const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${ isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : '' }` @@ -766,8 +808,8 @@ const convertDeprecatedTemplateKey = ( deprecatedKey: string, key?: string ): void => { - if (template[deprecatedKey as keyof ChargingStationTemplate] !== undefined) { - if (key !== undefined) { + if (template[deprecatedKey as keyof ChargingStationTemplate] != null) { + if (key != null) { (template as unknown as Record)[key] = template[deprecatedKey as keyof ChargingStationTemplate] } @@ -939,7 +981,7 @@ export const prepareChargingProfileKind = ( if (connectorStatus?.transactionStarted === true) { chargingProfile.chargingSchedule.startSchedule = connectorStatus.transactionStart } - // FIXME: Handle relative charging profile duration + // FIXME: handle relative charging profile duration break } return true @@ -951,16 +993,14 @@ export const canProceedChargingProfile = ( logPrefix: string ): boolean => { if ( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - (isValidTime(chargingProfile.validFrom) && isBefore(currentDate, chargingProfile.validFrom!)) || - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - (isValidTime(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!)) + (isValidDate(chargingProfile.validFrom) && isBefore(currentDate, chargingProfile.validFrom)) || + (isValidDate(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo)) ) { logger.debug( `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${ chargingProfile.chargingProfileId } is not valid for the current date ${ - currentDate instanceof Date ? currentDate.toISOString() : currentDate + isDate(currentDate) ? currentDate.toISOString() : currentDate }` ) return false @@ -974,7 +1014,7 @@ export const canProceedChargingProfile = ( ) return false } - if (!isValidTime(chargingProfile.chargingSchedule.startSchedule)) { + if (!isValidDate(chargingProfile.chargingSchedule.startSchedule)) { logger.error( `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has an invalid startSchedule date defined` ) @@ -1028,7 +1068,7 @@ const prepareRecurringChargingProfile = ( ): boolean => { const chargingSchedule = chargingProfile.chargingSchedule let recurringIntervalTranslated = false - let recurringInterval: Interval + let recurringInterval: Interval | undefined switch (chargingProfile.recurrencyKind) { case RecurrencyKindType.DAILY: recurringInterval = { @@ -1087,13 +1127,11 @@ const prepareRecurringChargingProfile = ( `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${ chargingProfile.recurrencyKind } charging profile id ${chargingProfile.chargingProfileId} recurrency time interval [${toDate( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - recurringInterval!.start + recurringInterval?.start as Date ).toISOString()}, ${toDate( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - recurringInterval!.end + recurringInterval?.end as Date ).toISOString()}] has not been properly translated to current date ${ - currentDate instanceof Date ? currentDate.toISOString() : currentDate + isDate(currentDate) ? currentDate.toISOString() : currentDate } ` ) }