-import crypto from 'crypto';
+import crypto from 'node:crypto';
import path from 'path';
import { fileURLToPath } from 'url';
import moment from 'moment';
+import type ChargingStation from './ChargingStation';
import BaseError from '../exception/BaseError';
import type { ChargingStationInfo } from '../types/ChargingStationInfo';
import {
CurrentType,
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';
+import type { OCPP16BootNotificationRequest } from '../types/ocpp/1.6/Requests';
+import { BootReasonEnumType, type OCPP20BootNotificationRequest } from '../types/ocpp/2.0/Requests';
import {
- type BootNotificationRequest,
- IncomingRequestCommand,
- RequestCommand,
-} from '../types/ocpp/Requests';
+ type ChargingProfile,
+ ChargingRateUnitType,
+ type 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 { ACElectricUtils, DCElectricUtils } from '../utils/ElectricUtils';
import logger from '../utils/Logger';
import Utils from '../utils/Utils';
-import type ChargingStation from './ChargingStation';
-import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
const moduleName = 'ChargingStationUtils';
// 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 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) && {
...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
}),
+ // FIXME?: Should a firmware version change always reference a new configuration file?
...(!Utils.isUndefined(stationTemplate.firmwareVersion) && {
firmwareVersion: stationTemplate.firmwareVersion,
}),
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 createBootNotificationRequest(
- stationInfo: ChargingStationInfo
+ stationInfo: ChargingStationInfo,
+ bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
): 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: bootReason,
+ 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 {
if (!Utils.isUndefined(template[key])) {
logger.warn(
`${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${
- logMsgToAppend && '. ' + logMsgToAppend
+ !Utils.isEmptyString(logMsgToAppend) && `. ${logMsgToAppend}`
}`
);
}
public static createSerialNumber(
stationTemplate: ChargingStationTemplate,
- stationInfo: ChargingStationInfo = {} as ChargingStationInfo,
+ stationInfo: ChargingStationInfo,
params: {
randomSerialNumberUpperCase?: boolean;
randomSerialNumber?: boolean;
})
: '';
stationInfo.chargePointSerialNumber =
- stationTemplate?.chargePointSerialNumberPrefix &&
- stationTemplate.chargePointSerialNumberPrefix + serialNumberSuffix;
+ !Utils.isEmptyString(stationTemplate?.chargePointSerialNumberPrefix) &&
+ `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`;
stationInfo.chargeBoxSerialNumber =
- stationTemplate?.chargeBoxSerialNumberPrefix &&
- stationTemplate.chargeBoxSerialNumberPrefix + serialNumberSuffix;
+ !Utils.isEmptyString(stationTemplate?.chargeBoxSerialNumberPrefix) &&
+ `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`;
stationInfo.meterSerialNumber =
- stationTemplate?.meterSerialNumberPrefix &&
- stationTemplate.meterSerialNumberPrefix + serialNumberSuffix;
+ !Utils.isEmptyString(stationTemplate?.meterSerialNumberPrefix) &&
+ `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`;
}
public static propagateSerialNumber(
stationTemplate: ChargingStationTemplate,
stationInfoSrc: ChargingStationInfo,
- stationInfoDst: ChargingStationInfo = {} as ChargingStationInfo
+ stationInfoDst: ChargingStationInfo
) {
if (!stationInfoSrc || !stationTemplate) {
throw new BaseError(
return unitDivider;
}
- public static setChargingProfile(
+ public static getChargingStationConnectorChargingProfilesPowerLimit(
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`
+ 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)
);
- 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`
+ if (!Utils.isEmptyArray(chargingProfiles)) {
+ const result = ChargingStationUtils.getLimitFromChargingProfiles(
+ chargingProfiles,
+ chargingStation.logPrefix()
);
- chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
+ 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;
+ }
+ }
}
- 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;
- }
- });
+ 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);
}
- !cpReplaced && chargingStation.getConnectorStatus(connectorId).chargingProfiles?.push(cp);
+ 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 {ChargingProfile[]} chargingProfiles
- * @param {string} logPrefix
- * @returns {{ limit, matchingChargingProfile }}
+ * @param chargingProfiles -
+ * @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 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;
- }
- 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];
- }
- }
- 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}`
- );
- }
-
- 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)
- )
- );
- }
-
- 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;
- }
- logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
- return false;
- }
-
- public static isIncomingRequestCommandSupported(
- command: IncomingRequestCommand,
- chargingStation: ChargingStation
- ): boolean {
- const isIncomingRequestCommand = Object.values(IncomingRequestCommand).includes(command);
- if (
- isIncomingRequestCommand === true &&
- !chargingStation.stationInfo?.commandsSupport?.incomingCommands
- ) {
- return true;
- } else if (
- isIncomingRequestCommand === true &&
- chargingStation.stationInfo?.commandsSupport?.incomingCommands
- ) {
- return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
- }
- logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
- return false;
- }
-
private static getRandomSerialNumberSuffix(params?: {
randomBytesLength?: number;
upperCase?: boolean;