X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationUtils.ts;h=49a61101533043ae15cb09c6783eb197bf452cfd;hb=007b5bdeabda751743fdff8faac672b3ec57fb61;hp=9b371d140732db0bbcb7cff08b864ea9f2a1cd8d;hpb=d812bdcbd13b39bf895bb3e01c0556d87c35a6d1;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index 9b371d14..49a61101 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -1,34 +1,41 @@ import crypto from 'node:crypto'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import chalk from 'chalk'; import moment from 'moment'; -import type ChargingStation from './ChargingStation'; -import BaseError from '../exception/BaseError'; -import type { ChargingStationInfo } from '../types/ChargingStationInfo'; +import type { ChargingStation } from './internal'; +import { BaseError } from '../exception'; import { AmpereUnits, + AvailabilityType, + type BootNotificationRequest, + BootReasonEnumType, + type ChargingProfile, + ChargingProfileKindType, + ChargingRateUnitType, + type ChargingSchedulePeriod, + type ChargingStationInfo, type ChargingStationTemplate, + type ConnectorStatus, CurrentType, + type EvseTemplate, + type OCPP16BootNotificationRequest, + type OCPP20BootNotificationRequest, + OCPPVersion, + RecurrencyKindType, Voltage, -} from '../types/ChargingStationTemplate'; -import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile'; -import type { OCPP16BootNotificationRequest } from '../types/ocpp/1.6/Requests'; -import { BootReasonEnumType, type OCPP20BootNotificationRequest } from '../types/ocpp/2.0/Requests'; +} from '../types'; import { - type ChargingProfile, - ChargingRateUnitType, - type ChargingSchedulePeriod, -} from '../types/ocpp/ChargingProfile'; -import { OCPPVersion } from '../types/ocpp/OCPPVersion'; -import type { BootNotificationRequest } from '../types/ocpp/Requests'; -import { WorkerProcessType } from '../types/Worker'; -import Configuration from '../utils/Configuration'; -import Constants from '../utils/Constants'; -import { ACElectricUtils, DCElectricUtils } from '../utils/ElectricUtils'; -import logger from '../utils/Logger'; -import Utils from '../utils/Utils'; + ACElectricUtils, + Configuration, + Constants, + DCElectricUtils, + Utils, + logger, +} from '../utils'; +import { WorkerProcessType } from '../worker'; const moduleName = 'ChargingStationUtils'; @@ -43,7 +50,7 @@ export class ChargingStationUtils { ): string { // In case of multiple instances: add instance index to charging station id const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0; - const idSuffix = stationTemplate.nameSuffix ?? ''; + const idSuffix = stationTemplate?.nameSuffix ?? ''; const idStr = `000000000${index.toString()}`; return stationTemplate?.fixedName ? stationTemplate.baseName @@ -62,12 +69,6 @@ export class ChargingStationUtils { ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && { chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix, }), - // FIXME?: Should a firmware version change always reference a new configuration file? - ...(!Utils.isUndefined(stationTemplate.firmwareVersion) && { - firmwareVersion: stationTemplate.firmwareVersion, - }), - ...(!Utils.isUndefined(stationTemplate.iccid) && { iccid: stationTemplate.iccid }), - ...(!Utils.isUndefined(stationTemplate.imsi) && { imsi: stationTemplate.imsi }), ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && { meterSerialNumber: stationTemplate.meterSerialNumberPrefix, }), @@ -86,12 +87,26 @@ export class ChargingStationUtils { .digest('hex'); } - public static getTemplateMaxNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { - const templateConnectors = stationTemplate?.Connectors; - if (!templateConnectors) { + public static checkChargingStation(chargingStation: ChargingStation, logPrefix: string): boolean { + if (chargingStation.started === false && chargingStation.starting === false) { + logger.warn(`${logPrefix} charging station is stopped, cannot proceed`); + return false; + } + return true; + } + + 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; } - return Object.keys(templateConnectors).length; + return Object.keys(connectors).length; } public static checkTemplateMaxConnectors( @@ -110,18 +125,28 @@ export class ChargingStationUtils { } } - public static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { + public static getConfiguredNumberOfConnectors(stationInfo: ChargingStationInfo): number { let configuredMaxConnectors: number; - if (Utils.isEmptyArray(stationTemplate.numberOfConnectors) === false) { - const numberOfConnectors = stationTemplate.numberOfConnectors as number[]; + if (Utils.isNotEmptyArray(stationInfo.numberOfConnectors) === true) { + const numberOfConnectors = stationInfo.numberOfConnectors as number[]; configuredMaxConnectors = numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)]; - } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) { - configuredMaxConnectors = stationTemplate.numberOfConnectors as number; - } else { - configuredMaxConnectors = stationTemplate?.Connectors[0] - ? ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate) - 1 - : ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate); + } else if (Utils.isUndefined(stationInfo.numberOfConnectors) === false) { + configuredMaxConnectors = stationInfo.numberOfConnectors as number; + } else if (stationInfo.Connectors && !stationInfo.Evses) { + configuredMaxConnectors = stationInfo?.Connectors[0] + ? ChargingStationUtils.getMaxNumberOfConnectors(stationInfo.Connectors) - 1 + : ChargingStationUtils.getMaxNumberOfConnectors(stationInfo.Connectors); + } else if (stationInfo.Evses && !stationInfo.Connectors) { + configuredMaxConnectors = 0; + for (const evse in stationInfo.Evses) { + if (evse === '0') { + continue; + } + configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors( + stationInfo.Evses[evse].Connectors + ); + } } return configuredMaxConnectors; } @@ -138,6 +163,85 @@ export class ChargingStationUtils { } } + 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 connectorsMap; + } + + public static initializeConnectorsMapStatus( + connectors: Map, + logPrefix: string + ): void { + 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 @@ -191,39 +295,37 @@ export class ChargingStationUtils { } public static workerPoolInUse(): boolean { - return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes( + return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes( Configuration.getWorker().processType ); } public static workerDynamicPoolInUse(): boolean { - return Configuration.getWorker().processType === WorkerProcessType.DYNAMIC_POOL; + return Configuration.getWorker().processType === WorkerProcessType.dynamicPool; } - public static warnDeprecatedTemplateKey( - template: ChargingStationTemplate, - key: string, + public static warnTemplateKeysDeprecation( templateFile: string, - logPrefix: string, - logMsgToAppend = '' - ): void { - if (!Utils.isUndefined(template[key])) { - logger.warn( - `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${ - logMsgToAppend && `. ${logMsgToAppend}` - }` + stationTemplate: ChargingStationTemplate, + logPrefix: string + ) { + const templateKeys: { key: string; deprecatedKey: string }[] = [ + { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' }, + { key: 'idTagsFile', deprecatedKey: 'authorizationFile' }, + ]; + for (const templateKey of templateKeys) { + ChargingStationUtils.warnDeprecatedTemplateKey( + stationTemplate, + templateKey.deprecatedKey, + templateFile, + logPrefix, + `Use '${templateKey.key}' instead` + ); + ChargingStationUtils.convertDeprecatedTemplateKey( + stationTemplate, + templateKey.deprecatedKey, + templateKey.key ); - } - } - - public static convertDeprecatedTemplateKey( - template: ChargingStationTemplate, - deprecatedKey: string, - key: string - ): void { - if (!Utils.isUndefined(template[deprecatedKey])) { - template[key] = template[deprecatedKey] as unknown; - delete template[deprecatedKey]; } } @@ -268,15 +370,19 @@ export class ChargingStationUtils { upperCase: params.randomSerialNumberUpperCase, }) : ''; - stationInfo.chargePointSerialNumber = - !Utils.isEmptyString(stationTemplate?.chargePointSerialNumberPrefix) && - `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`; - stationInfo.chargeBoxSerialNumber = - !Utils.isEmptyString(stationTemplate?.chargeBoxSerialNumberPrefix) && - `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`; - stationInfo.meterSerialNumber = - !Utils.isEmptyString(stationTemplate?.meterSerialNumberPrefix) && - `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`; + 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; } public static propagateSerialNumber( @@ -321,20 +427,20 @@ export class ChargingStationUtils { connectorId: number ): number | undefined { let limit: number, matchingChargingProfile: ChargingProfile; - let chargingProfiles: ChargingProfile[] = []; // Get charging profiles for connector and sort by stack level - chargingProfiles = chargingStation - .getConnectorStatus(connectorId) - ?.chargingProfiles?.sort((a, b) => b.stackLevel - a.stackLevel); + const chargingProfiles = + 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( - ...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.isEmptyArray(chargingProfiles)) { + if (Utils.isNotEmptyArray(chargingProfiles)) { const result = ChargingStationUtils.getLimitFromChargingProfiles( chargingProfiles, chargingStation.logPrefix() @@ -398,19 +504,59 @@ export class ChargingStationUtils { return defaultVoltageOut; } - public static getAuthorizationFile(stationInfo: ChargingStationInfo): string | undefined { + public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined { return ( - stationInfo.authorizationFile && + stationInfo.idTagsFile && path.join( path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'), 'assets', - path.basename(stationInfo.authorizationFile) + path.basename(stationInfo.idTagsFile) ) ); } + 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, + logMsgToAppend = '' + ): void { + if (!Utils.isUndefined(template[key])) { + const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${ + Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : '' + }`; + logger.warn(`${logPrefix} ${logMsg}`); + console.warn(chalk.yellow(`${logMsg}`)); + } + } + + private static convertDeprecatedTemplateKey( + template: ChargingStationTemplate, + deprecatedKey: string, + key: string + ): void { + if (!Utils.isUndefined(template[deprecatedKey])) { + template[key] = template[deprecatedKey] as unknown; + delete template[deprecatedKey]; + } + } + /** - * 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 - @@ -424,10 +570,16 @@ export class ChargingStationUtils { matchingChargingProfile: ChargingProfile; } | null { const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`; + const currentMoment = moment(); + const currentDate = new Date(); for (const chargingProfile of chargingProfiles) { // Set helpers - const currentMoment = moment(); const chargingSchedule = chargingProfile.chargingSchedule; + if (!chargingSchedule?.startSchedule) { + logger.warn( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}` + ); + } // Check type (recurring) and if it is already active // Adjust the daily recurring schedule to today if ( @@ -435,8 +587,12 @@ export class ChargingStationUtils { chargingProfile.recurrencyKind === RecurrencyKindType.DAILY && currentMoment.isAfter(chargingSchedule.startSchedule) ) { - const currentDate = new Date(); - chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule); + if (!(chargingSchedule?.startSchedule instanceof Date)) { + logger.warn( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object` + ); + chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule); + } chargingSchedule.startSchedule.setFullYear( currentDate.getFullYear(), currentDate.getMonth(),