-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 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';
): 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) && {
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) {
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)];
}
public static createBootNotificationRequest(
- stationInfo: ChargingStationInfo
+ stationInfo: ChargingStationInfo,
+ bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
): BootNotificationRequest {
const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
switch (ocppVersion) {
case OCPPVersion.VERSION_20:
case OCPPVersion.VERSION_201:
return {
- reason: BootReasonEnumType.PowerUp,
+ reason: bootReason,
chargingStation: {
model: stationInfo.chargePointModel,
vendorName: stationInfo.chargePointVendor,
...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
serialNumber: stationInfo.chargeBoxSerialNumber,
}),
- modem: {
- ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
- ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
- },
+ ...((!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(
+ 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(
if (!Utils.isUndefined(template[key])) {
logger.warn(
`${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${
- logMsgToAppend && '. ' + logMsgToAppend
+ Utils.isNotEmptyString(logMsgToAppend) && `. ${logMsgToAppend}`
}`
);
}
public static createSerialNumber(
stationTemplate: ChargingStationTemplate,
- stationInfo: ChargingStationInfo = {} as ChargingStationInfo,
+ stationInfo: ChargingStationInfo,
params: {
randomSerialNumberUpperCase?: boolean;
randomSerialNumber?: boolean;
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(
return unitDivider;
}
+ public static getChargingStationConnectorChargingProfilesPowerLimit(
+ chargingStation: ChargingStation,
+ connectorId: number
+ ): number | undefined {
+ let limit: number, matchingChargingProfile: ChargingProfile;
+ let chargingProfiles: ChargingProfile[] = [];
+ // Get charging profiles for connector and sort by stack level
+ chargingProfiles = chargingStation
+ .getConnectorStatus(connectorId)
+ ?.chargingProfiles?.sort((a, b) => b.stackLevel - a.stackLevel);
+ // 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 getAuthorizationFile(stationInfo: ChargingStationInfo): string | undefined {
+ return (
+ stationInfo.authorizationFile &&
+ path.join(
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
+ 'assets',
+ path.basename(stationInfo.authorizationFile)
+ )
+ );
+ }
+
/**
* Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
*
* @param logPrefix -
* @returns
*/
- public static getLimitFromChargingProfiles(
+ private static getLimitFromChargingProfiles(
chargingProfiles: ChargingProfile[],
logPrefix: string
): {
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;