X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationUtils.ts;h=4cf1b386fde85cd580e7b1a6f159bd7aafe4d125;hb=18bf82749012da310723996912f5239c20bdc4e7;hp=da1a8281d62a8a9c286595982361c4bf1e12c0f8;hpb=17ac262c08a637a9aef23c350176bf476ad212ef;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index da1a8281..4cf1b386 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -1,19 +1,36 @@ -import { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile'; -import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile'; -import ChargingStationTemplate, { AmpereUnits } from '../types/ChargingStationTemplate'; +import crypto from 'crypto'; +import path from 'path'; +import { fileURLToPath } from 'url'; -import { BootNotificationRequest } from '../types/ocpp/Requests'; -import ChargingStationInfo from '../types/ChargingStationInfo'; +import moment from 'moment'; + +import BaseError from '../exception/BaseError'; +import type { ChargingStationInfo } from '../types/ChargingStationInfo'; +import { + AmpereUnits, + type ChargingStationTemplate, + CurrentType, + 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 Utils from '../utils/Utils'; -import { WebSocketCloseEventStatusString } from '../types/WebSocket'; -import { WorkerProcessType } from '../types/Worker'; -import crypto from 'crypto'; import logger from '../utils/Logger'; -import moment from 'moment'; +import Utils from '../utils/Utils'; + +const moduleName = 'ChargingStationUtils'; export class ChargingStationUtils { + private constructor() { + // This is intentional + } + public static getChargingStationId( index: number, stationTemplate: ChargingStationTemplate @@ -22,7 +39,7 @@ export class ChargingStationUtils { const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0; const idSuffix = stationTemplate.nameSuffix ?? ''; const idStr = '000000000' + index.toString(); - return stationTemplate.fixedName + return stationTemplate?.fixedName ? stationTemplate.baseName : stationTemplate.baseName + '-' + @@ -31,94 +48,149 @@ export class ChargingStationUtils { idSuffix; } - public static getHashId(stationInfo: ChargingStationInfo): string { - const hashBootNotificationRequest = { - chargePointModel: stationInfo.chargePointModel, - chargePointVendor: stationInfo.chargePointVendor, - ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumberPrefix) && { - chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumberPrefix, + public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string { + const chargingStationInfo = { + chargePointModel: stationTemplate.chargePointModel, + chargePointVendor: stationTemplate.chargePointVendor, + ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && { + chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix, }), - ...(!Utils.isUndefined(stationInfo.chargePointSerialNumberPrefix) && { - chargePointSerialNumber: stationInfo.chargePointSerialNumberPrefix, + ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && { + chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix, }), - ...(!Utils.isUndefined(stationInfo.firmwareVersion) && { - firmwareVersion: stationInfo.firmwareVersion, + // FIXME?: Should a firmware version change always reference a new configuration file? + ...(!Utils.isUndefined(stationTemplate.firmwareVersion) && { + firmwareVersion: stationTemplate.firmwareVersion, }), - ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }), - ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }), - ...(!Utils.isUndefined(stationInfo.meterSerialNumberPrefix) && { - meterSerialNumber: stationInfo.meterSerialNumberPrefix, + ...(!Utils.isUndefined(stationTemplate.iccid) && { iccid: stationTemplate.iccid }), + ...(!Utils.isUndefined(stationTemplate.imsi) && { imsi: stationTemplate.imsi }), + ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && { + meterSerialNumber: stationTemplate.meterSerialNumberPrefix, }), - ...(!Utils.isUndefined(stationInfo.meterType) && { - meterType: stationInfo.meterType, + ...(!Utils.isUndefined(stationTemplate.meterType) && { + meterType: stationTemplate.meterType, }), }; return crypto .createHash(Constants.DEFAULT_HASH_ALGORITHM) - .update(JSON.stringify(hashBootNotificationRequest) + stationInfo.chargingStationId) + .update( + JSON.stringify(chargingStationInfo) + + ChargingStationUtils.getChargingStationId(index, stationTemplate) + ) .digest('hex'); } + public static getTemplateMaxNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { + const templateConnectors = stationTemplate?.Connectors; + if (!templateConnectors) { + return -1; + } + return Object.keys(templateConnectors).length; + } + + public static checkTemplateMaxConnectors( + templateMaxConnectors: number, + templateFile: string, + logPrefix: 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` + ); + } + } + + public static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { + let configuredMaxConnectors: number; + if (Utils.isEmptyArray(stationTemplate.numberOfConnectors) === false) { + 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 { + configuredMaxConnectors = stationTemplate?.Connectors[0] + ? ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate) - 1 + : ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate); + } + return configuredMaxConnectors; + } + + public static checkConfiguredMaxConnectors( + configuredMaxConnectors: number, + templateFile: string, + logPrefix: string + ): void { + if (configuredMaxConnectors <= 0) { + logger.warn( + `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors` + ); + } + } + public static createBootNotificationRequest( stationInfo: ChargingStationInfo ): BootNotificationRequest { - return { - chargePointModel: stationInfo.chargePointModel, - chargePointVendor: stationInfo.chargePointVendor, - ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && { - chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber, - }), - ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && { - chargePointSerialNumber: stationInfo.chargePointSerialNumber, - }), - ...(!Utils.isUndefined(stationInfo.firmwareVersion) && { - firmwareVersion: stationInfo.firmwareVersion, - }), - ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }), - ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }), - ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && { - meterSerialNumber: stationInfo.meterSerialNumber, - }), - ...(!Utils.isUndefined(stationInfo.meterType) && { - meterType: stationInfo.meterType, - }), - }; + const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16; + switch (ocppVersion) { + case OCPPVersion.VERSION_16: + return { + chargePointModel: stationInfo.chargePointModel, + chargePointVendor: stationInfo.chargePointVendor, + ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && { + chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber, + }), + ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && { + chargePointSerialNumber: stationInfo.chargePointSerialNumber, + }), + ...(!Utils.isUndefined(stationInfo.firmwareVersion) && { + firmwareVersion: stationInfo.firmwareVersion, + }), + ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }), + ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }), + ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && { + meterSerialNumber: stationInfo.meterSerialNumber, + }), + ...(!Utils.isUndefined(stationInfo.meterType) && { + meterType: stationInfo.meterType, + }), + } as OCPP16BootNotificationRequest; + case OCPPVersion.VERSION_20: + case OCPPVersion.VERSION_201: + return { + reason: BootReasonEnumType.PowerUp, + chargingStation: { + model: stationInfo.chargePointModel, + vendorName: stationInfo.chargePointVendor, + ...(!Utils.isUndefined(stationInfo.firmwareVersion) && { + firmwareVersion: stationInfo.firmwareVersion, + }), + ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && { + serialNumber: stationInfo.chargeBoxSerialNumber, + }), + ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && { + modem: { + ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }), + ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }), + }, + }), + }, + } as OCPP20BootNotificationRequest; + } } public static workerPoolInUse(): boolean { return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes( - Configuration.getWorkerProcess() + Configuration.getWorker().processType ); } public static workerDynamicPoolInUse(): boolean { - return Configuration.getWorkerProcess() === WorkerProcessType.DYNAMIC_POOL; - } - - /** - * Convert websocket error code to human readable string message - * - * @param code websocket error code - * @returns human readable string message - */ - public static getWebSocketCloseEventStatusString(code: number): string { - if (code >= 0 && code <= 999) { - return '(Unused)'; - } else if (code >= 1016) { - if (code <= 1999) { - return '(For WebSocket standard)'; - } else if (code <= 2999) { - return '(For WebSocket extensions)'; - } else if (code <= 3999) { - return '(For libraries and frameworks)'; - } else if (code <= 4999) { - return '(For applications)'; - } - } - if (!Utils.isUndefined(WebSocketCloseEventStatusString[code])) { - return WebSocketCloseEventStatusString[code] as string; - } - return '(Unknown)'; + return Configuration.getWorker().processType === WorkerProcessType.DYNAMIC_POOL; } public static warnDeprecatedTemplateKey( @@ -129,7 +201,6 @@ export class ChargingStationUtils { logMsgToAppend = '' ): void { if (!Utils.isUndefined(template[key])) { - // const logPrefixStr = ` ${chargingStationId} |`; logger.warn( `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${ logMsgToAppend && '. ' + logMsgToAppend @@ -149,30 +220,35 @@ export class ChargingStationUtils { } } - public static createStationInfoHash(stationInfo: ChargingStationInfo): ChargingStationInfo { - if (!Utils.isEmptyObject(stationInfo)) { - const previousInfoHash = stationInfo?.infoHash ?? ''; - delete stationInfo.infoHash; - const currentInfoHash = crypto - .createHash(Constants.DEFAULT_HASH_ALGORITHM) - .update(JSON.stringify(stationInfo)) - .digest('hex'); - if ( - Utils.isEmptyString(previousInfoHash) || - (!Utils.isEmptyString(previousInfoHash) && currentInfoHash !== previousInfoHash) - ) { - stationInfo.infoHash = currentInfoHash; - } else { - stationInfo.infoHash = previousInfoHash; - } - } - return stationInfo; + public static stationTemplateToStationInfo( + stationTemplate: ChargingStationTemplate + ): ChargingStationInfo { + stationTemplate = Utils.cloneObject(stationTemplate); + delete stationTemplate.power; + delete stationTemplate.powerUnit; + delete stationTemplate.Configuration; + delete stationTemplate.AutomaticTransactionGenerator; + delete stationTemplate.chargeBoxSerialNumberPrefix; + delete stationTemplate.chargePointSerialNumberPrefix; + delete stationTemplate.meterSerialNumberPrefix; + 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, - existingStationInfo?: ChargingStationInfo, - params: { randomSerialNumberUpperCase?: boolean; randomSerialNumber?: boolean } = { + params: { + randomSerialNumberUpperCase?: boolean; + randomSerialNumber?: boolean; + } = { randomSerialNumberUpperCase: true, randomSerialNumber: true, } @@ -180,29 +256,41 @@ export class ChargingStationUtils { params = params ?? {}; params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true; params.randomSerialNumber = params?.randomSerialNumber ?? true; - if (!Utils.isEmptyObject(existingStationInfo)) { - existingStationInfo?.chargePointSerialNumber && - (stationInfo.chargePointSerialNumber = existingStationInfo.chargePointSerialNumber); - existingStationInfo?.chargeBoxSerialNumber && - (stationInfo.chargeBoxSerialNumber = existingStationInfo.chargeBoxSerialNumber); - existingStationInfo?.meterSerialNumber && - (stationInfo.meterSerialNumber = existingStationInfo.meterSerialNumber); - } else { - const serialNumberSuffix = params?.randomSerialNumber - ? ChargingStationUtils.getRandomSerialNumberSuffix({ - upperCase: params.randomSerialNumberUpperCase, - }) - : ''; - stationInfo.chargePointSerialNumber = - stationInfo?.chargePointSerialNumberPrefix && - stationInfo.chargePointSerialNumberPrefix + serialNumberSuffix; - stationInfo.chargeBoxSerialNumber = - stationInfo?.chargeBoxSerialNumberPrefix && - stationInfo.chargeBoxSerialNumberPrefix + serialNumberSuffix; - stationInfo.meterSerialNumber = - stationInfo?.meterSerialNumberPrefix && - stationInfo.meterSerialNumberPrefix + serialNumberSuffix; + const serialNumberSuffix = params?.randomSerialNumber + ? ChargingStationUtils.getRandomSerialNumberSuffix({ + upperCase: params.randomSerialNumberUpperCase, + }) + : ''; + stationInfo.chargePointSerialNumber = + stationTemplate?.chargePointSerialNumberPrefix && + stationTemplate.chargePointSerialNumberPrefix + serialNumberSuffix; + stationInfo.chargeBoxSerialNumber = + stationTemplate?.chargeBoxSerialNumberPrefix && + stationTemplate.chargeBoxSerialNumberPrefix + serialNumberSuffix; + stationInfo.meterSerialNumber = + stationTemplate?.meterSerialNumberPrefix && + stationTemplate.meterSerialNumberPrefix + serialNumberSuffix; + } + + public static propagateSerialNumber( + stationTemplate: ChargingStationTemplate, + stationInfoSrc: ChargingStationInfo, + stationInfoDst: ChargingStationInfo + ) { + if (!stationInfoSrc || !stationTemplate) { + throw new BaseError( + 'Missing charging station template or existing configuration to propagate serial number' + ); } + stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber + ? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber) + : stationInfoDst?.chargePointSerialNumber && delete stationInfoDst.chargePointSerialNumber; + stationTemplate?.chargeBoxSerialNumberPrefix && stationInfoSrc?.chargeBoxSerialNumber + ? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber) + : stationInfoDst?.chargeBoxSerialNumber && delete stationInfoDst.chargeBoxSerialNumber; + stationTemplate?.meterSerialNumberPrefix && stationInfoSrc?.meterSerialNumber + ? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber) + : stationInfoDst?.meterSerialNumber && delete stationInfoDst.meterSerialNumber; } public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number { @@ -224,9 +312,9 @@ export class ChargingStationUtils { /** * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority) * - * @param {ChargingProfile[]} chargingProfiles - * @param {string} logPrefix - * @returns {{ limit, matchingChargingProfile }} + * @param chargingProfiles - + * @param logPrefix - + * @returns */ public static getLimitFromChargingProfiles( chargingProfiles: ChargingProfile[], @@ -235,6 +323,7 @@ export class ChargingStationUtils { limit: number; matchingChargingProfile: ChargingProfile; } | null { + const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`; for (const chargingProfile of chargingProfiles) { // Set helpers const currentMoment = moment(); @@ -278,10 +367,7 @@ export class ChargingStationUtils { limit: schedulePeriod.limit, matchingChargingProfile: chargingProfile, }; - logger.debug( - `${logPrefix} Matching charging profile found for power limitation: %j`, - result - ); + logger.debug(debugLogMsg, result); return result; } // Find the right schedule period @@ -295,10 +381,7 @@ export class ChargingStationUtils { limit: lastButOneSchedule.limit, matchingChargingProfile: chargingProfile, }; - logger.debug( - `${logPrefix} Matching charging profile found for power limitation: %j`, - result - ); + logger.debug(debugLogMsg, result); return result; } // Keep it @@ -314,10 +397,7 @@ export class ChargingStationUtils { limit: lastButOneSchedule.limit, matchingChargingProfile: chargingProfile, }; - logger.debug( - `${logPrefix} Matching charging profile found for power limitation: %j`, - result - ); + logger.debug(debugLogMsg, result); return result; } } @@ -326,6 +406,38 @@ 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;