-import { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile';
-import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
+import crypto from 'crypto';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import moment from 'moment';
+
+import BaseError from '../exception/BaseError';
+import type ChargingStationInfo from '../types/ChargingStationInfo';
import ChargingStationTemplate, {
AmpereUnits,
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 { BootNotificationRequest } from '../types/ocpp/Requests';
-import ChargingStation from './ChargingStation';
-import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
-import ChargingStationInfo from '../types/ChargingStationInfo';
+import {
+ BootNotificationRequest,
+ IncomingRequestCommand,
+ RequestCommand,
+} from '../types/ocpp/Requests';
+import { WorkerProcessType } from '../types/Worker';
import Configuration from '../utils/Configuration';
import Constants from '../utils/Constants';
-import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates';
-import { StandardParametersKey } from '../types/ocpp/Configuration';
-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';
+import type ChargingStation from './ChargingStation';
+import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
+
+const moduleName = 'ChargingStationUtils';
export class ChargingStationUtils {
+ private constructor() {
+ // This is intentional
+ }
+
public static getChargingStationId(
index: number,
stationTemplate: ChargingStationTemplate
idSuffix;
}
- public static getHashId(stationInfo: ChargingStationInfo): string {
+ public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
const hashBootNotificationRequest = {
- chargePointModel: stationInfo.chargePointModel,
- chargePointVendor: stationInfo.chargePointVendor,
- ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumberPrefix) && {
- chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumberPrefix,
+ 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,
+ ...(!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(hashBootNotificationRequest) +
+ 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(
+ index: number,
+ stationTemplate: ChargingStationTemplate
+ ): number {
+ let configuredMaxConnectors: number;
+ if (!Utils.isEmptyArray(stationTemplate.numberOfConnectors)) {
+ const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
+ // Distribute evenly the number of connectors
+ configuredMaxConnectors = numberOfConnectors[(index - 1) % numberOfConnectors.length];
+ } else if (!Utils.isUndefined(stationTemplate.numberOfConnectors)) {
+ 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 {
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(
}
}
- public static createStationInfoHash(stationInfo: ChargingStationInfo): ChargingStationInfo {
- const previousInfoHash = stationInfo?.infoHash ?? '';
+ 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;
- const currentInfoHash = crypto
+ stationInfo.infoHash = 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 createSerialNumber(
- stationInfo: ChargingStationInfo,
- existingStationInfo?: ChargingStationInfo | null,
- params: { randomSerialNumberUpperCase?: boolean; randomSerialNumber?: boolean } = {
+ stationTemplate: ChargingStationTemplate,
+ stationInfo: ChargingStationInfo = {} as ChargingStationInfo,
+ params: {
+ randomSerialNumberUpperCase?: boolean;
+ randomSerialNumber?: boolean;
+ } = {
randomSerialNumberUpperCase: true,
randomSerialNumber: true,
}
params = params ?? {};
params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
params.randomSerialNumber = params?.randomSerialNumber ?? true;
- if (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 = {} 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;
}
public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
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();
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
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
limit: lastButOneSchedule.limit,
matchingChargingProfile: chargingProfile,
};
- logger.debug(
- `${logPrefix} Matching charging profile found for power limitation: %j`,
- result
- );
+ logger.debug(debugLogMsg, result);
return result;
}
}
templateFile: string,
logPrefix: string
): Voltage {
- const errMsg = `${logPrefix} Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
+ 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_400;
break;
default:
- logger.error(errMsg);
- throw new Error(errMsg);
+ logger.error(`${logPrefix} ${errMsg}`);
+ throw new BaseError(errMsg);
}
return defaultVoltageOut;
}
}
}
if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
- const errorMsg = `${chargingStation.logPrefix()} Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
- logger.error(errorMsg);
- throw new Error(errorMsg);
+ 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 && !chargingStation.stationInfo?.commandsSupport?.outgoingCommands) {
+ return true;
+ } else if (isRequestCommand && 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 &&
+ !chargingStation.stationInfo?.commandsSupport?.incomingCommands
+ ) {
+ return true;
+ } else if (
+ isIncomingRequestCommand &&
+ 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;