fix: fix promises return type
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
index e378fdec87bd26832ce1abf4ebe2cddd53b8a1b9..78b73647456bdf8568ebf61aaac281e698aad322 100644 (file)
-import crypto from 'crypto';
-import path from 'path';
-import { fileURLToPath } from 'url';
+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 BaseError from '../exception/BaseError';
-import type { ChargingStationInfo } from '../types/ChargingStationInfo';
+import type { ChargingStation } from './ChargingStation';
+import { BaseError } from '../exception';
 import {
   AmpereUnits,
+  AvailabilityType,
+  type BootNotificationRequest,
+  BootReasonEnumType,
+  type ChargingProfile,
+  ChargingProfileKindType,
+  ChargingRateUnitType,
+  type ChargingSchedulePeriod,
+  type ChargingStationInfo,
   type ChargingStationTemplate,
+  ChargingStationWorkerMessageEvents,
+  ConnectorPhaseRotation,
+  type ConnectorStatus,
+  ConnectorStatusEnum,
   CurrentType,
+  type EvseTemplate,
+  type OCPP16BootNotificationRequest,
+  type OCPP20BootNotificationRequest,
+  OCPPVersion,
+  RecurrencyKindType,
   Voltage,
-} from '../types/ChargingStationTemplate';
-import type { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates';
-import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
-import type { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile';
-import { StandardParametersKey } from '../types/ocpp/Configuration';
-import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
+} from '../types';
 import {
-  type BootNotificationRequest,
-  IncomingRequestCommand,
-  RequestCommand,
-} 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';
-import type ChargingStation from './ChargingStation';
-import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
+  ACElectricUtils,
+  Constants,
+  DCElectricUtils,
+  cloneObject,
+  convertToInt,
+  isEmptyObject,
+  isEmptyString,
+  isNotEmptyArray,
+  isNotEmptyString,
+  isNullOrUndefined,
+  isUndefined,
+  logger,
+  secureRandom,
+} from '../utils';
 
 const moduleName = 'ChargingStationUtils';
 
-export class ChargingStationUtils {
-  private constructor() {
-    // This is intentional
+export const getChargingStationId = (
+  index: number,
+  stationTemplate: ChargingStationTemplate,
+): 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()}`;
+  return stationTemplate?.fixedName
+    ? stationTemplate.baseName
+    : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
+        idStr.length - 4,
+      )}${idSuffix}`;
+};
+
+export const countReservableConnectors = (connectors: Map<number, ConnectorStatus>) => {
+  let reservableConnectors = 0;
+  for (const [connectorId, connectorStatus] of connectors) {
+    if (connectorId === 0) {
+      continue;
+    }
+    if (connectorStatus.status === ConnectorStatusEnum.Available) {
+      ++reservableConnectors;
+    }
   }
+  return reservableConnectors;
+};
+
+export const getHashId = (index: number, stationTemplate: ChargingStationTemplate): string => {
+  const chargingStationInfo = {
+    chargePointModel: stationTemplate.chargePointModel,
+    chargePointVendor: stationTemplate.chargePointVendor,
+    ...(!isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
+      chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
+    }),
+    ...(!isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
+      chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
+    }),
+    ...(!isUndefined(stationTemplate.meterSerialNumberPrefix) && {
+      meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
+    }),
+    ...(!isUndefined(stationTemplate.meterType) && {
+      meterType: stationTemplate.meterType,
+    }),
+  };
+  return createHash(Constants.DEFAULT_HASH_ALGORITHM)
+    .update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
+    .digest('hex');
+};
 
-  public static getChargingStationId(
-    index: number,
-    stationTemplate: ChargingStationTemplate
-  ): 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();
-    return stationTemplate?.fixedName
-      ? stationTemplate.baseName
-      : stationTemplate.baseName +
-          '-' +
-          instanceIndex.toString() +
-          idStr.substring(idStr.length - 4) +
-          idSuffix;
+export const 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 getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
-    const hashBootNotificationRequest = {
-      chargePointModel: stationTemplate.chargePointModel,
-      chargePointVendor: stationTemplate.chargePointVendor,
-      ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
-        chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
-      }),
-      ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
-        chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
-      }),
-      ...(!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,
-      }),
-      ...(!Utils.isUndefined(stationTemplate.meterType) && {
-        meterType: stationTemplate.meterType,
-      }),
-    };
-    return crypto
-      .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-      .update(
-        JSON.stringify(hashBootNotificationRequest) +
-          ChargingStationUtils.getChargingStationId(index, stationTemplate)
-      )
-      .digest('hex');
+export const 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 getTemplateMaxNumberOfConnectors(stationTemplate: ChargingStationTemplate): number {
-    const templateConnectors = stationTemplate?.Connectors;
-    if (!templateConnectors) {
-      return -1;
-    }
-    return Object.keys(templateConnectors).length;
+export const getMaxNumberOfEvses = (evses: Record<string, EvseTemplate>): number => {
+  if (!evses) {
+    return -1;
   }
+  return Object.keys(evses).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`
-      );
-    }
+const getMaxNumberOfConnectors = (connectors: Record<string, ConnectorStatus>): number => {
+  if (!connectors) {
+    return -1;
   }
+  return Object.keys(connectors).length;
+};
 
-  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;
+export const 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 checkConfiguredMaxConnectors(
-    configuredMaxConnectors: number,
-    templateFile: string,
-    logPrefix: string
-  ): void {
-    if (configuredMaxConnectors <= 0) {
-      logger.warn(
-        `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
-      );
-    }
+export const checkTemplate = (
+  stationTemplate: ChargingStationTemplate,
+  logPrefix: string,
+  templateFile: string,
+): void => {
+  if (isNullOrUndefined(stationTemplate)) {
+    const errorMsg = `Failed to read charging station template file ${templateFile}`;
+    logger.error(`${logPrefix} ${errorMsg}`);
+    throw new BaseError(errorMsg);
+  }
+  if (isEmptyObject(stationTemplate)) {
+    const errorMsg = `Empty charging station information from template file ${templateFile}`;
+    logger.error(`${logPrefix} ${errorMsg}`);
+    throw new BaseError(errorMsg);
+  }
+  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 (isNullOrUndefined(stationTemplate.idTagsFile) || 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 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,
-      }),
-    };
+export const checkConnectorsConfiguration = (
+  stationTemplate: ChargingStationTemplate,
+  logPrefix: string,
+  templateFile: string,
+): {
+  configuredMaxConnectors: number;
+  templateMaxConnectors: number;
+  templateMaxAvailableConnectors: number;
+} => {
+  const configuredMaxConnectors = getConfiguredNumberOfConnectors(stationTemplate);
+  checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile);
+  const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors!);
+  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 workerPoolInUse(): boolean {
-    return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes(
-      Configuration.getWorker().processType
+export const checkStationInfoConnectorStatus = (
+  connectorId: number,
+  connectorStatus: ConnectorStatus,
+  logPrefix: string,
+  templateFile: string,
+): void => {
+  if (!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 workerDynamicPoolInUse(): boolean {
-    return Configuration.getWorker().processType === WorkerProcessType.DYNAMIC_POOL;
+export const buildConnectorsMap = (
+  connectors: Record<string, ConnectorStatus>,
+  logPrefix: string,
+  templateFile: string,
+): Map<number, ConnectorStatus> => {
+  const connectorsMap = new Map<number, ConnectorStatus>();
+  if (getMaxNumberOfConnectors(connectors) > 0) {
+    for (const connector in connectors) {
+      const connectorStatus = connectors[connector];
+      const connectorId = convertToInt(connector);
+      checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile);
+      connectorsMap.set(connectorId, cloneObject<ConnectorStatus>(connectorStatus));
+    }
+  } else {
+    logger.warn(
+      `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`,
+    );
   }
+  return connectorsMap;
+};
 
-  public static warnDeprecatedTemplateKey(
-    template: ChargingStationTemplate,
-    key: string,
-    templateFile: string,
-    logPrefix: string,
-    logMsgToAppend = ''
-  ): void {
-    if (!Utils.isUndefined(template[key])) {
+export const initializeConnectorsMapStatus = (
+  connectors: Map<number, ConnectorStatus>,
+  logPrefix: string,
+): void => {
+  for (const connectorId of connectors.keys()) {
+    if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
       logger.warn(
-        `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${
-          logMsgToAppend && '. ' + logMsgToAppend
-        }`
+        `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${connectors.get(
+          connectorId,
+        )?.transactionId}`,
       );
     }
-  }
-
-  public static convertDeprecatedTemplateKey(
-    template: ChargingStationTemplate,
-    deprecatedKey: string,
-    key: string
-  ): void {
-    if (!Utils.isUndefined(template[deprecatedKey])) {
-      template[key] = template[deprecatedKey] as unknown;
-      delete template[deprecatedKey];
+    if (connectorId === 0) {
+      connectors.get(connectorId)!.availability = AvailabilityType.Operative;
+      if (isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
+        connectors.get(connectorId)!.chargingProfiles = [];
+      }
+    } else if (
+      connectorId > 0 &&
+      isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
+    ) {
+      initializeConnectorStatus(connectors.get(connectorId)!);
     }
   }
+};
 
-  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;
-  }
+export const 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 createStationInfoHash(stationInfo: ChargingStationInfo): void {
-    delete stationInfo.infoHash;
-    stationInfo.infoHash = crypto
-      .createHash(Constants.DEFAULT_HASH_ALGORITHM)
-      .update(JSON.stringify(stationInfo))
-      .digest('hex');
+export const createBootNotificationRequest = (
+  stationInfo: ChargingStationInfo,
+  bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp,
+): BootNotificationRequest => {
+  const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
+  switch (ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      return {
+        chargePointModel: stationInfo.chargePointModel,
+        chargePointVendor: stationInfo.chargePointVendor,
+        ...(!isUndefined(stationInfo.chargeBoxSerialNumber) && {
+          chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
+        }),
+        ...(!isUndefined(stationInfo.chargePointSerialNumber) && {
+          chargePointSerialNumber: stationInfo.chargePointSerialNumber,
+        }),
+        ...(!isUndefined(stationInfo.firmwareVersion) && {
+          firmwareVersion: stationInfo.firmwareVersion,
+        }),
+        ...(!isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
+        ...(!isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
+        ...(!isUndefined(stationInfo.meterSerialNumber) && {
+          meterSerialNumber: stationInfo.meterSerialNumber,
+        }),
+        ...(!isUndefined(stationInfo.meterType) && {
+          meterType: stationInfo.meterType,
+        }),
+      } as OCPP16BootNotificationRequest;
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      return {
+        reason: bootReason,
+        chargingStation: {
+          model: stationInfo.chargePointModel,
+          vendorName: stationInfo.chargePointVendor,
+          ...(!isUndefined(stationInfo.firmwareVersion) && {
+            firmwareVersion: stationInfo.firmwareVersion,
+          }),
+          ...(!isUndefined(stationInfo.chargeBoxSerialNumber) && {
+            serialNumber: stationInfo.chargeBoxSerialNumber,
+          }),
+          ...((!isUndefined(stationInfo.iccid) || !isUndefined(stationInfo.imsi)) && {
+            modem: {
+              ...(!isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
+              ...(!isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
+            },
+          }),
+        },
+      } as OCPP20BootNotificationRequest;
   }
+};
 
-  public static createSerialNumber(
-    stationTemplate: ChargingStationTemplate,
-    stationInfo: ChargingStationInfo = {} as ChargingStationInfo,
-    params: {
-      randomSerialNumberUpperCase?: boolean;
-      randomSerialNumber?: boolean;
-    } = {
-      randomSerialNumberUpperCase: true,
-      randomSerialNumber: true,
-    }
-  ): void {
-    params = params ?? {};
-    params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
-    params.randomSerialNumber = params?.randomSerialNumber ?? true;
-    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;
+export const warnTemplateKeysDeprecation = (
+  stationTemplate: ChargingStationTemplate,
+  logPrefix: string,
+  templateFile: string,
+) => {
+  const templateKeys: { deprecatedKey: string; key?: string }[] = [
+    { deprecatedKey: 'supervisionUrl', key: 'supervisionUrls' },
+    { deprecatedKey: 'authorizationFile', key: 'idTagsFile' },
+    { deprecatedKey: 'payloadSchemaValidation', key: 'ocppStrictCompliance' },
+  ];
+  for (const templateKey of templateKeys) {
+    warnDeprecatedTemplateKey(
+      stationTemplate,
+      templateKey.deprecatedKey,
+      logPrefix,
+      templateFile,
+      !isUndefined(templateKey.key) ? `Use '${templateKey.key}' instead` : undefined,
+    );
+    convertDeprecatedTemplateKey(stationTemplate, templateKey.deprecatedKey, templateKey.key);
   }
+};
 
-  public static propagateSerialNumber(
-    stationTemplate: ChargingStationTemplate,
-    stationInfoSrc: ChargingStationInfo,
-    stationInfoDst: ChargingStationInfo = {} as 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;
-  }
+export const stationTemplateToStationInfo = (
+  stationTemplate: ChargingStationTemplate,
+): ChargingStationInfo => {
+  stationTemplate = cloneObject<ChargingStationTemplate>(stationTemplate);
+  delete stationTemplate.power;
+  delete stationTemplate.powerUnit;
+  delete stationTemplate.Connectors;
+  delete stationTemplate.Evses;
+  delete stationTemplate.Configuration;
+  delete stationTemplate.AutomaticTransactionGenerator;
+  delete stationTemplate.chargeBoxSerialNumberPrefix;
+  delete stationTemplate.chargePointSerialNumberPrefix;
+  delete stationTemplate.meterSerialNumberPrefix;
+  return stationTemplate as unknown as ChargingStationInfo;
+};
 
-  public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
-    let unitDivider = 1;
-    switch (stationInfo.amperageLimitationUnit) {
-      case AmpereUnits.DECI_AMPERE:
-        unitDivider = 10;
-        break;
-      case AmpereUnits.CENTI_AMPERE:
-        unitDivider = 100;
-        break;
-      case AmpereUnits.MILLI_AMPERE:
-        unitDivider = 1000;
-        break;
-    }
-    return unitDivider;
+export const createSerialNumber = (
+  stationTemplate: ChargingStationTemplate,
+  stationInfo: ChargingStationInfo,
+  params: {
+    randomSerialNumberUpperCase?: boolean;
+    randomSerialNumber?: boolean;
+  } = {
+    randomSerialNumberUpperCase: true,
+    randomSerialNumber: true,
+  },
+): void => {
+  params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params };
+  const serialNumberSuffix = params?.randomSerialNumber
+    ? getRandomSerialNumberSuffix({
+        upperCase: params.randomSerialNumberUpperCase,
+      })
+    : '';
+  isNotEmptyString(stationTemplate?.chargePointSerialNumberPrefix) &&
+    (stationInfo.chargePointSerialNumber = `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
+  isNotEmptyString(stationTemplate?.chargeBoxSerialNumberPrefix) &&
+    (stationInfo.chargeBoxSerialNumber = `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
+  isNotEmptyString(stationTemplate?.meterSerialNumberPrefix) &&
+    (stationInfo.meterSerialNumber = `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
+};
+
+export const 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 setChargingProfile(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    cp: ChargingProfile
-  ): void {
-    if (Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
-      );
-      chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
-    }
-    if (Array.isArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles) === false) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
-      );
-      chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
-    }
-    let cpReplaced = false;
-    if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
-      chargingStation
-        .getConnectorStatus(connectorId)
-        .chargingProfiles?.forEach((chargingProfile: ChargingProfile, index: number) => {
-          if (
-            chargingProfile.chargingProfileId === cp.chargingProfileId ||
-            (chargingProfile.stackLevel === cp.stackLevel &&
-              chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
-          ) {
-            chargingStation.getConnectorStatus(connectorId).chargingProfiles[index] = cp;
-            cpReplaced = true;
-          }
-        });
-    }
-    !cpReplaced && chargingStation.getConnectorStatus(connectorId).chargingProfiles?.push(cp);
+export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInfo): number => {
+  let unitDivider = 1;
+  switch (stationInfo.amperageLimitationUnit) {
+    case AmpereUnits.DECI_AMPERE:
+      unitDivider = 10;
+      break;
+    case AmpereUnits.CENTI_AMPERE:
+      unitDivider = 100;
+      break;
+    case AmpereUnits.MILLI_AMPERE:
+      unitDivider = 1000;
+      break;
   }
+  return unitDivider;
+};
 
-  /**
-   * 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 }}
-   */
-  public static getLimitFromChargingProfiles(
-    chargingProfiles: ChargingProfile[],
-    logPrefix: string
-  ): {
-    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();
-      const chargingSchedule = chargingProfile.chargingSchedule;
-      // Check type (recurring) and if it is already active
-      // Adjust the daily recurring schedule to today
-      if (
-        chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
-        chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
-        currentMoment.isAfter(chargingSchedule.startSchedule)
-      ) {
-        const currentDate = new Date();
-        chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
-        chargingSchedule.startSchedule.setFullYear(
-          currentDate.getFullYear(),
-          currentDate.getMonth(),
-          currentDate.getDate()
-        );
-        // Check if the start of the schedule is yesterday
-        if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
-          chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
-        }
-      } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
-        return null;
+export const getChargingStationConnectorChargingProfilesPowerLimit = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+): number | undefined => {
+  let limit: number | undefined, matchingChargingProfile: ChargingProfile | undefined;
+  // Get charging profiles for connector and sort by stack level
+  const chargingProfiles =
+    cloneObject<ChargingProfile[]>(
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles!,
+    )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? [];
+  // Get profiles on connector 0
+  if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
+    chargingProfiles.push(
+      ...cloneObject<ChargingProfile[]>(
+        chargingStation.getConnectorStatus(0)!.chargingProfiles!,
+      ).sort((a, b) => b.stackLevel - a.stackLevel),
+    );
+  }
+  if (isNotEmptyArray(chargingProfiles)) {
+    const result = getLimitFromChargingProfiles(chargingProfiles, chargingStation.logPrefix());
+    if (!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!);
       }
-      // Check if the charging profile is active
-      if (
-        moment(chargingSchedule.startSchedule)
-          .add(chargingSchedule.duration, 's')
-          .isAfter(currentMoment)
-      ) {
-        let lastButOneSchedule: ChargingSchedulePeriod;
-        // Search the right schedule period
-        for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
-          // Handling of only one period
-          if (
-            chargingSchedule.chargingSchedulePeriod.length === 1 &&
-            schedulePeriod.startPeriod === 0
-          ) {
-            const result = {
-              limit: schedulePeriod.limit,
-              matchingChargingProfile: chargingProfile,
-            };
-            logger.debug(debugLogMsg, result);
-            return result;
-          }
-          // Find the right schedule period
-          if (
-            moment(chargingSchedule.startSchedule)
-              .add(schedulePeriod.startPeriod, 's')
-              .isAfter(currentMoment)
-          ) {
-            // Found the schedule: last but one is the correct one
-            const result = {
-              limit: lastButOneSchedule.limit,
-              matchingChargingProfile: chargingProfile,
-            };
-            logger.debug(debugLogMsg, result);
-            return result;
-          }
-          // Keep it
-          lastButOneSchedule = schedulePeriod;
-          // Handle the last schedule period
-          if (
-            schedulePeriod.startPeriod ===
-            chargingSchedule.chargingSchedulePeriod[
-              chargingSchedule.chargingSchedulePeriod.length - 1
-            ].startPeriod
-          ) {
-            const result = {
-              limit: lastButOneSchedule.limit,
-              matchingChargingProfile: chargingProfile,
-            };
-            logger.debug(debugLogMsg, result);
-            return result;
-          }
-        }
+      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 null;
   }
+  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;
+export const getDefaultVoltageOut = (
+  currentType: CurrentType,
+  logPrefix: string,
+  templateFile: string,
+): Voltage => {
+  const errorMsg = `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} ${errorMsg}`);
+      throw new BaseError(errorMsg);
   }
+  return defaultVoltageOut;
+};
 
-  public static getSampledValueTemplate(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
-    phase?: MeterValuePhase
-  ): SampledValueTemplate | undefined {
-    const onPhaseStr = phase ? `on phase ${phase} ` : '';
-    if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
-      logger.warn(
-        `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
-      );
-      return;
-    }
-    if (
-      measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-      !ChargingStationConfigurationUtils.getConfigurationKey(
-        chargingStation,
-        StandardParametersKey.MeterValuesSampledData
-      )?.value.includes(measurand)
-    ) {
-      logger.debug(
-        `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
-          StandardParametersKey.MeterValuesSampledData
-        }' OCPP parameter`
-      );
-      return;
+export const getIdTagsFile = (stationInfo: ChargingStationInfo): string | undefined => {
+  return (
+    stationInfo.idTagsFile &&
+    join(dirname(fileURLToPath(import.meta.url)), 'assets', basename(stationInfo.idTagsFile))
+  );
+};
+
+export const waitChargingStationEvents = async (
+  emitter: EventEmitter,
+  event: ChargingStationWorkerMessageEvents,
+  eventsToWait: number,
+): Promise<number> => {
+  return new Promise<number>((resolve) => {
+    let events = 0;
+    if (eventsToWait === 0) {
+      resolve(events);
     }
-    const sampledValueTemplates: SampledValueTemplate[] =
-      chargingStation.getConnectorStatus(connectorId).MeterValues;
-    for (
-      let index = 0;
-      !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length;
-      index++
-    ) {
-      if (
-        Constants.SUPPORTED_MEASURANDS.includes(
-          sampledValueTemplates[index]?.measurand ??
-            MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-        ) === false
-      ) {
-        logger.warn(
-          `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
-        );
-      } else if (
-        phase &&
-        sampledValueTemplates[index]?.phase === phase &&
-        sampledValueTemplates[index]?.measurand === measurand &&
-        ChargingStationConfigurationUtils.getConfigurationKey(
-          chargingStation,
-          StandardParametersKey.MeterValuesSampledData
-        )?.value.includes(measurand) === true
-      ) {
-        return sampledValueTemplates[index];
-      } else if (
-        !phase &&
-        !sampledValueTemplates[index].phase &&
-        sampledValueTemplates[index]?.measurand === measurand &&
-        ChargingStationConfigurationUtils.getConfigurationKey(
-          chargingStation,
-          StandardParametersKey.MeterValuesSampledData
-        )?.value.includes(measurand) === true
-      ) {
-        return sampledValueTemplates[index];
-      } else if (
-        measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-        (!sampledValueTemplates[index].measurand ||
-          sampledValueTemplates[index].measurand === measurand)
-      ) {
-        return sampledValueTemplates[index];
+    emitter.on(event, () => {
+      ++events;
+      if (events === eventsToWait) {
+        resolve(events);
       }
+    });
+  });
+};
+
+const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => {
+  let configuredMaxConnectors = 0;
+  if (isNotEmptyArray(stationTemplate.numberOfConnectors) === true) {
+    const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
+    configuredMaxConnectors =
+      numberOfConnectors[Math.floor(secureRandom() * numberOfConnectors.length)];
+  } else if (isUndefined(stationTemplate.numberOfConnectors) === false) {
+    configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
+  } else if (stationTemplate.Connectors && !stationTemplate.Evses) {
+    configuredMaxConnectors = stationTemplate.Connectors[0]
+      ? getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
+      : getMaxNumberOfConnectors(stationTemplate.Connectors);
+  } else if (stationTemplate.Evses && !stationTemplate.Connectors) {
+    for (const evse in stationTemplate.Evses) {
+      if (evse === '0') {
+        continue;
+      }
+      configuredMaxConnectors += getMaxNumberOfConnectors(stationTemplate.Evses[evse].Connectors);
     }
-    if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-      const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
-      logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
-      throw new BaseError(errorMsg);
-    }
-    logger.debug(
-      `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
+  }
+  return configuredMaxConnectors;
+};
+
+const checkConfiguredMaxConnectors = (
+  configuredMaxConnectors: number,
+  logPrefix: string,
+  templateFile: string,
+): void => {
+  if (configuredMaxConnectors <= 0) {
+    logger.warn(
+      `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`,
     );
   }
+};
 
-  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)
-      )
+const 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`,
     );
   }
+};
 
-  public static isRequestCommandSupported(
-    command: RequestCommand,
-    chargingStation: ChargingStation
-  ): boolean {
-    const isRequestCommand = Object.values(RequestCommand).includes(command);
-    if (
-      isRequestCommand === true &&
-      !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
-    ) {
-      return true;
-    } else if (
-      isRequestCommand === true &&
-      chargingStation.stationInfo?.commandsSupport?.outgoingCommands
-    ) {
-      return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
+const 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 (isUndefined(connectorStatus.chargingProfiles)) {
+    connectorStatus.chargingProfiles = [];
+  }
+};
+
+const warnDeprecatedTemplateKey = (
+  template: ChargingStationTemplate,
+  key: string,
+  logPrefix: string,
+  templateFile: string,
+  logMsgToAppend = '',
+): void => {
+  if (!isUndefined(template[key as keyof ChargingStationTemplate])) {
+    const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
+      isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
+    }`;
+    logger.warn(`${logPrefix} ${logMsg}`);
+    console.warn(chalk.yellow(`${logMsg}`));
+  }
+};
+
+const convertDeprecatedTemplateKey = (
+  template: ChargingStationTemplate,
+  deprecatedKey: string,
+  key?: string,
+): void => {
+  if (!isUndefined(template[deprecatedKey as keyof ChargingStationTemplate])) {
+    if (!isUndefined(key)) {
+      (template as unknown as Record<string, unknown>)[key!] =
+        template[deprecatedKey as keyof ChargingStationTemplate];
     }
-    logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
-    return false;
+    delete template[deprecatedKey as keyof ChargingStationTemplate];
   }
+};
 
-  public static isIncomingRequestCommandSupported(
-    command: IncomingRequestCommand,
-    chargingStation: ChargingStation
-  ): boolean {
-    const isIncomingRequestCommand = Object.values(IncomingRequestCommand).includes(command);
+/**
+ * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
+ *
+ * @param chargingProfiles -
+ * @param logPrefix -
+ * @returns
+ */
+const getLimitFromChargingProfiles = (
+  chargingProfiles: ChargingProfile[],
+  logPrefix: string,
+): {
+  limit: number;
+  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 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 (
-      isIncomingRequestCommand === true &&
-      !chargingStation.stationInfo?.commandsSupport?.incomingCommands
+      chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
+      chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
+      currentMoment.isAfter(chargingSchedule.startSchedule)
     ) {
-      return true;
-    } else if (
-      isIncomingRequestCommand === true &&
-      chargingStation.stationInfo?.commandsSupport?.incomingCommands
+      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(),
+        currentDate.getDate(),
+      );
+      // Check if the start of the schedule is yesterday
+      if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
+        chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
+      }
+    } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
+      return null;
+    }
+    // Check if the charging profile is active
+    if (
+      moment(chargingSchedule.startSchedule)
+        .add(chargingSchedule.duration, 's')
+        .isAfter(currentMoment)
     ) {
-      return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
+      let lastButOneSchedule: ChargingSchedulePeriod | undefined;
+      // Search the right schedule period
+      for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
+        // Handling of only one period
+        if (
+          chargingSchedule.chargingSchedulePeriod.length === 1 &&
+          schedulePeriod.startPeriod === 0
+        ) {
+          const result = {
+            limit: schedulePeriod.limit,
+            matchingChargingProfile: chargingProfile,
+          };
+          logger.debug(debugLogMsg, result);
+          return result;
+        }
+        // Find the right schedule period
+        if (
+          moment(chargingSchedule.startSchedule)
+            .add(schedulePeriod.startPeriod, 's')
+            .isAfter(currentMoment)
+        ) {
+          // Found the schedule: last but one is the correct one
+          const result = {
+            limit: lastButOneSchedule!.limit,
+            matchingChargingProfile: chargingProfile,
+          };
+          logger.debug(debugLogMsg, result);
+          return result;
+        }
+        // Keep it
+        lastButOneSchedule = schedulePeriod;
+        // Handle the last schedule period
+        if (
+          schedulePeriod.startPeriod ===
+          chargingSchedule.chargingSchedulePeriod[
+            chargingSchedule.chargingSchedulePeriod.length - 1
+          ].startPeriod
+        ) {
+          const result = {
+            limit: lastButOneSchedule.limit,
+            matchingChargingProfile: chargingProfile,
+          };
+          logger.debug(debugLogMsg, result);
+          return result;
+        }
+      }
     }
-    logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
-    return false;
   }
+  return null;
+};
 
-  private static getRandomSerialNumberSuffix(params?: {
-    randomBytesLength?: number;
-    upperCase?: boolean;
-  }): string {
-    const randomSerialNumberSuffix = crypto
-      .randomBytes(params?.randomBytesLength ?? 16)
-      .toString('hex');
-    if (params?.upperCase) {
-      return randomSerialNumberSuffix.toUpperCase();
-    }
-    return randomSerialNumberSuffix;
+const getRandomSerialNumberSuffix = (params?: {
+  randomBytesLength?: number;
+  upperCase?: boolean;
+}): string => {
+  const randomSerialNumberSuffix = randomBytes(params?.randomBytesLength ?? 16).toString('hex');
+  if (params?.upperCase) {
+    return randomSerialNumberSuffix.toUpperCase();
   }
-}
+  return randomSerialNumberSuffix;
+};