X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationUtils.ts;h=1059a1d0c8b4da1215eb1184b1a25614ed32b07e;hb=411894569d4a0333a4e38e911a178520a69448cd;hp=daf48ac3475de0b918de6e653fce5c14ce6aadad;hpb=98fc1389a2464ce8738047f8990731ae31938ee5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index daf48ac3..1059a1d0 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -1,28 +1,38 @@ -import crypto from 'crypto'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import crypto from 'node:crypto'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import chalk from 'chalk'; import moment from 'moment'; -import BaseError from '../exception/BaseError'; -import type { ChargingStationInfo } from '../types/ChargingStationInfo'; +import type { ChargingStation } from './internal'; +import { BaseError } from '../exception'; import { AmpereUnits, + type BootNotificationRequest, + BootReasonEnumType, + type ChargingProfile, + ChargingProfileKindType, + ChargingRateUnitType, + type ChargingSchedulePeriod, + type ChargingStationInfo, type ChargingStationTemplate, CurrentType, + 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, OCPP20BootNotificationRequest } from '../types/ocpp/2.0/Requests'; -import type { ChargingProfile, 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 logger from '../utils/Logger'; -import Utils from '../utils/Utils'; +} from '../types'; +import { + ACElectricUtils, + Configuration, + Constants, + DCElectricUtils, + Utils, + logger, +} from '../utils'; +import { WorkerProcessType } from '../worker'; const moduleName = 'ChargingStationUtils'; @@ -37,19 +47,17 @@ 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 idStr = '000000000' + index.toString(); + const idSuffix = stationTemplate?.nameSuffix ?? ''; + const idStr = `000000000${index.toString()}`; return stationTemplate?.fixedName ? stationTemplate.baseName - : stationTemplate.baseName + - '-' + - instanceIndex.toString() + - idStr.substring(idStr.length - 4) + - idSuffix; + : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring( + idStr.length - 4 + )}${idSuffix}`; } public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string { - const hashBootNotificationRequest = { + const chargingStationInfo = { chargePointModel: stationTemplate.chargePointModel, chargePointVendor: stationTemplate.chargePointVendor, ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && { @@ -58,12 +66,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, }), @@ -74,12 +76,22 @@ export class ChargingStationUtils { return crypto .createHash(Constants.DEFAULT_HASH_ALGORITHM) .update( - JSON.stringify(hashBootNotificationRequest) + - ChargingStationUtils.getChargingStationId(index, stationTemplate) + `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId( + index, + stationTemplate + )}` ) .digest('hex'); } + 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 getTemplateMaxNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { const templateConnectors = stationTemplate?.Connectors; if (!templateConnectors) { @@ -106,7 +118,7 @@ export class ChargingStationUtils { public static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { let configuredMaxConnectors: number; - if (Utils.isEmptyArray(stationTemplate.numberOfConnectors) === false) { + if (Utils.isNotEmptyArray(stationTemplate.numberOfConnectors) === true) { const numberOfConnectors = stationTemplate.numberOfConnectors as number[]; configuredMaxConnectors = numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)]; @@ -133,7 +145,8 @@ export class ChargingStationUtils { } public static createBootNotificationRequest( - stationInfo: ChargingStationInfo + stationInfo: ChargingStationInfo, + bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp ): BootNotificationRequest { const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16; switch (ocppVersion) { @@ -162,7 +175,7 @@ export class ChargingStationUtils { case OCPPVersion.VERSION_20: case OCPPVersion.VERSION_201: return { - reason: BootReasonEnumType.PowerUp, + reason: bootReason, chargingStation: { model: stationInfo.chargePointModel, vendorName: stationInfo.chargePointVendor, @@ -184,39 +197,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]; } } @@ -244,7 +255,7 @@ export class ChargingStationUtils { public static createSerialNumber( stationTemplate: ChargingStationTemplate, - stationInfo: ChargingStationInfo = {} as ChargingStationInfo, + stationInfo: ChargingStationInfo, params: { randomSerialNumberUpperCase?: boolean; randomSerialNumber?: boolean; @@ -261,21 +272,25 @@ export class ChargingStationUtils { upperCase: params.randomSerialNumberUpperCase, }) : ''; - stationInfo.chargePointSerialNumber = - stationTemplate?.chargePointSerialNumberPrefix && - stationTemplate.chargePointSerialNumberPrefix + serialNumberSuffix; - stationInfo.chargeBoxSerialNumber = - stationTemplate?.chargeBoxSerialNumberPrefix && - stationTemplate.chargeBoxSerialNumberPrefix + serialNumberSuffix; - stationInfo.meterSerialNumber = - 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( stationTemplate: ChargingStationTemplate, stationInfoSrc: ChargingStationInfo, - stationInfoDst: ChargingStationInfo = {} as ChargingStationInfo + stationInfoDst: ChargingStationInfo ) { if (!stationInfoSrc || !stationTemplate) { throw new BaseError( @@ -309,6 +324,127 @@ export class ChargingStationUtils { return unitDivider; } + public static getChargingStationConnectorChargingProfilesPowerLimit( + chargingStation: ChargingStation, + connectorId: number + ): number | undefined { + 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) ?? [] + ); + // Get profiles on connector 0 + if (chargingStation.getConnectorStatus(0)?.chargingProfiles) { + chargingProfiles.push( + ...chargingStation + .getConnectorStatus(0) + .chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel) + ); + } + if (Utils.isNotEmptyArray(chargingProfiles)) { + const result = ChargingStationUtils.getLimitFromChargingProfiles( + chargingProfiles, + chargingStation.logPrefix() + ); + if (!Utils.isNullOrUndefined(result)) { + limit = result?.limit; + matchingChargingProfile = result?.matchingChargingProfile; + switch (chargingStation.getCurrentOutType()) { + case CurrentType.AC: + limit = + matchingChargingProfile.chargingSchedule.chargingRateUnit === + ChargingRateUnitType.WATT + ? limit + : ACElectricUtils.powerTotal( + chargingStation.getNumberOfPhases(), + chargingStation.getVoltageOut(), + limit + ); + break; + case CurrentType.DC: + limit = + matchingChargingProfile.chargingSchedule.chargingRateUnit === + ChargingRateUnitType.WATT + ? limit + : DCElectricUtils.power(chargingStation.getVoltageOut(), limit); + } + const connectorMaximumPower = + chargingStation.getMaximumPower() / chargingStation.powerDivider; + if (limit > connectorMaximumPower) { + logger.error( + `${chargingStation.logPrefix()} Charging profile id ${ + matchingChargingProfile.chargingProfileId + } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`, + result + ); + limit = connectorMaximumPower; + } + } + } + return limit; + } + + public static getDefaultVoltageOut( + currentType: CurrentType, + templateFile: string, + logPrefix: string + ): Voltage { + const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`; + let defaultVoltageOut: number; + switch (currentType) { + case CurrentType.AC: + defaultVoltageOut = Voltage.VOLTAGE_230; + break; + case CurrentType.DC: + defaultVoltageOut = Voltage.VOLTAGE_400; + break; + default: + logger.error(`${logPrefix} ${errMsg}`); + throw new BaseError(errMsg); + } + return defaultVoltageOut; + } + + 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) + ) + ); + } + + 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) * @@ -316,7 +452,7 @@ export class ChargingStationUtils { * @param logPrefix - * @returns */ - public static getLimitFromChargingProfiles( + private static getLimitFromChargingProfiles( chargingProfiles: ChargingProfile[], logPrefix: string ): { @@ -324,10 +460,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 ( @@ -335,8 +477,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(), @@ -406,38 +552,6 @@ export class ChargingStationUtils { return null; } - public static getDefaultVoltageOut( - currentType: CurrentType, - templateFile: string, - logPrefix: string - ): Voltage { - const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`; - let defaultVoltageOut: number; - switch (currentType) { - case CurrentType.AC: - defaultVoltageOut = Voltage.VOLTAGE_230; - break; - case CurrentType.DC: - defaultVoltageOut = Voltage.VOLTAGE_400; - break; - default: - logger.error(`${logPrefix} ${errMsg}`); - throw new BaseError(errMsg); - } - return defaultVoltageOut; - } - - public static getAuthorizationFile(stationInfo: ChargingStationInfo): string | undefined { - return ( - stationInfo.authorizationFile && - path.join( - path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'), - 'assets', - path.basename(stationInfo.authorizationFile) - ) - ); - } - private static getRandomSerialNumberSuffix(params?: { randomBytesLength?: number; upperCase?: boolean;