-import { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile';
-import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
-import ChargingStationTemplate, { AmpereUnits } from '../types/ChargingStationTemplate';
-
-import { BootNotificationRequest } from '../types/ocpp/Requests';
-import ChargingStationInfo from '../types/ChargingStationInfo';
-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';
-
-export class ChargingStationUtils {
- 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;
- }
-
- public static getHashId(stationInfo: ChargingStationInfo): string {
- const hashBootNotificationRequest = {
- chargePointModel: stationInfo.chargePointModel,
- chargePointVendor: stationInfo.chargePointVendor,
- ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumberPrefix) && {
- chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumberPrefix,
- }),
- ...(!Utils.isUndefined(stationInfo.chargePointSerialNumberPrefix) && {
- chargePointSerialNumber: stationInfo.chargePointSerialNumberPrefix,
- }),
- ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
- firmwareVersion: stationInfo.firmwareVersion,
- }),
- ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
- ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
- ...(!Utils.isUndefined(stationInfo.meterSerialNumberPrefix) && {
- meterSerialNumber: stationInfo.meterSerialNumberPrefix,
- }),
- ...(!Utils.isUndefined(stationInfo.meterType) && {
- meterType: stationInfo.meterType,
- }),
- };
- return crypto
- .createHash(Constants.DEFAULT_HASH_ALGORITHM)
- .update(JSON.stringify(hashBootNotificationRequest) + stationInfo.chargingStationId)
- .digest('hex');
- }
-
- 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,
- }),
- };
- }
-
- public static workerPoolInUse(): boolean {
- return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes(
- Configuration.getWorkerProcess()
+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 {
+ addDays,
+ addSeconds,
+ addWeeks,
+ differenceInDays,
+ differenceInSeconds,
+ differenceInWeeks,
+ isAfter,
+ isBefore,
+ isDate,
+ isWithinInterval,
+ toDate,
+} from 'date-fns';
+
+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';
+import {
+ ACElectricUtils,
+ Constants,
+ DCElectricUtils,
+ cloneObject,
+ convertToDate,
+ convertToInt,
+ isArraySorted,
+ isEmptyObject,
+ isEmptyString,
+ isNotEmptyArray,
+ isNotEmptyString,
+ isNullOrUndefined,
+ isUndefined,
+ isValidTime,
+ logger,
+ secureRandom,
+} from '../utils';
+
+const moduleName = 'ChargingStationUtils';
+
+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');
+};
+
+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;
+};
+
+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}`;
+ }
+};
+
+export const getMaxNumberOfEvses = (evses: Record<string, EvseTemplate>): number => {
+ if (!evses) {
+ return -1;
+ }
+ return Object.keys(evses).length;
+};
+
+const getMaxNumberOfConnectors = (connectors: Record<string, ConnectorStatus>): number => {
+ if (!connectors) {
+ return -1;
+ }
+ return Object.keys(connectors).length;
+};
+
+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;
+};
+
+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`,