import merge from 'just-merge';
import WebSocket, { type RawData } from 'ws';
+import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator';
+import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel';
+import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
+import { ChargingStationUtils } from './ChargingStationUtils';
+import { IdTagsCache } from './IdTagsCache';
import {
- AutomaticTransactionGenerator,
- ChargingStationConfigurationUtils,
- ChargingStationUtils,
- ChargingStationWorkerBroadcastChannel,
- IdTagsCache,
- MessageChannelUtils,
- SharedLRUCache,
-} from './internal';
-import {
- // OCPP16IncomingRequestService,
+ OCPP16IncomingRequestService,
OCPP16RequestService,
- // OCPP16ResponseService,
+ OCPP16ResponseService,
OCPP16ServiceUtils,
OCPP20IncomingRequestService,
OCPP20RequestService,
- // OCPP20ResponseService,
+ OCPP20ResponseService,
type OCPPIncomingRequestService,
type OCPPRequestService,
- // OCPPServiceUtils,
+ OCPPServiceUtils,
} from './ocpp';
-import { OCPP16IncomingRequestService } from './ocpp/1.6/OCPP16IncomingRequestService';
-import { OCPP16ResponseService } from './ocpp/1.6/OCPP16ResponseService';
-import { OCPP20ResponseService } from './ocpp/2.0/OCPP20ResponseService';
-import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils';
+import { SharedLRUCache } from './SharedLRUCache';
import { BaseError, OCPPError } from '../exception';
import { PerformanceStatistics } from '../performance';
import {
type BootNotificationRequest,
type BootNotificationResponse,
type CachedRequest,
- type ChargingStationAutomaticTransactionGeneratorConfiguration,
type ChargingStationConfiguration,
type ChargingStationInfo,
type ChargingStationOcppConfiguration,
} from '../types';
import {
ACElectricUtils,
+ AsyncLock,
+ AsyncLockType,
Configuration,
Constants,
DCElectricUtils,
+ ErrorUtils,
FileUtils,
+ MessageChannelUtils,
Utils,
+ buildChargingStationAutomaticTransactionGeneratorConfiguration,
+ buildConnectorsStatus,
+ buildEvsesStatus,
logger,
} from '../utils';
public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
public wsConnection!: WebSocket | null;
- public connectors: Map<number, ConnectorStatus>;
+ public readonly connectors: Map<number, ConnectorStatus>;
public readonly evses: Map<number, EvseStatus>;
public readonly requests: Map<string, CachedRequest>;
public performanceStatistics!: PerformanceStatistics | undefined;
return new URL(
`${
this.getSupervisionUrlOcppConfiguration() &&
- Utils.isNotEmptyString(this.getSupervisionUrlOcppKey())
+ Utils.isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
+ Utils.isNotEmptyString(
+ ChargingStationConfigurationUtils.getConfigurationKey(
+ this,
+ this.getSupervisionUrlOcppKey()
+ )?.value
+ )
? ChargingStationConfigurationUtils.getConfigurationKey(
this,
this.getSupervisionUrlOcppKey()
- )?.value
+ ).value
: this.configuredSupervisionUrl.href
}/${this.stationInfo.chargingStationId}`
);
};
public hasIdTags(): boolean {
- const idTagsFile = ChargingStationUtils.getIdTagsFile(this.stationInfo);
- return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile));
+ return Utils.isNotEmptyArray(
+ this.idTagsCache.getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo))
+ );
}
public getEnableStatistics(): boolean {
return this?.bootNotificationResponse?.status;
}
- public isInUnknownState(): boolean {
+ public inUnknownState(): boolean {
return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status);
}
- public isInPendingState(): boolean {
+ public inPendingState(): boolean {
return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING;
}
- public isInAcceptedState(): boolean {
+ public inAcceptedState(): boolean {
return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED;
}
- public isInRejectedState(): boolean {
+ public inRejectedState(): boolean {
return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED;
}
public isRegistered(): boolean {
return (
- this.isInUnknownState() === false &&
- (this.isInAcceptedState() === true || this.isInPendingState() === true)
+ this.inUnknownState() === false &&
+ (this.inAcceptedState() === true || this.inPendingState() === true)
);
}
public getAutomaticTransactionGeneratorConfiguration():
| AutomaticTransactionGeneratorConfiguration
| undefined {
+ let automaticTransactionGeneratorConfiguration:
+ | AutomaticTransactionGeneratorConfiguration
+ | undefined;
const automaticTransactionGeneratorConfigurationFromFile =
this.getConfigurationFromFile()?.automaticTransactionGenerator;
if (automaticTransactionGeneratorConfigurationFromFile) {
- return automaticTransactionGeneratorConfigurationFromFile;
+ automaticTransactionGeneratorConfiguration =
+ automaticTransactionGeneratorConfigurationFromFile;
+ } else {
+ automaticTransactionGeneratorConfiguration =
+ this.getTemplateFromFile()?.AutomaticTransactionGenerator;
}
- return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+ return {
+ ...Constants.DEFAULT_ATG_CONFIGURATION,
+ ...automaticTransactionGeneratorConfiguration,
+ };
}
public startAutomaticTransactionGenerator(connectorIds?: number[]): void {
this.templateFileHash = template.templateHash;
}
} catch (error) {
- FileUtils.handleFileException(
+ ErrorUtils.handleFileException(
this.templateFile,
FileType.ChargingStationTemplate,
error as NodeJS.ErrnoException,
private getStationInfoFromTemplate(): ChargingStationInfo {
const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile();
- ChargingStationUtils.checkTemplateFile(stationTemplate, this.logPrefix(), this.templateFile);
+ ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
ChargingStationUtils.warnTemplateKeysDeprecation(
stationTemplate,
this.logPrefix(),
private initialize(): void {
const stationTemplate = this.getTemplateFromFile();
- ChargingStationUtils.checkTemplateFile(stationTemplate, this.logPrefix(), this.templateFile);
+ ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
this.configurationFile = path.join(
path.dirname(this.templateFile.replace('station-templates', 'configurations')),
`${ChargingStationUtils.getHashId(this.index, stationTemplate)}.json`
private initializeConnectorsOrEvsesFromFile(configuration: ChargingStationConfiguration): void {
if (configuration?.connectorsStatus && !configuration?.evsesStatus) {
- this.connectors = new Map<number, ConnectorStatus>(
- configuration?.connectorsStatus.map((connectorStatus, connectorId) => [
- connectorId,
- connectorStatus,
- ])
- );
+ for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
+ this.connectors.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
+ }
} else if (configuration?.evsesStatus && !configuration?.connectorsStatus) {
for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
- const evseStatus = Utils.cloneObject(evseStatusConfiguration);
+ const evseStatus = Utils.cloneObject<EvseStatusConfiguration>(evseStatusConfiguration);
delete evseStatus.connectorsStatus;
this.evses.set(evseId, {
- ...evseStatus,
+ ...(evseStatus as EvseStatus),
connectors: new Map<number, ConnectorStatus>(
evseStatusConfiguration.connectorsStatus.map((connectorStatus, connectorId) => [
connectorId,
private getConfigurationFromFile(): ChargingStationConfiguration | undefined {
let configuration: ChargingStationConfiguration | undefined;
- if (this.configurationFile && fs.existsSync(this.configurationFile)) {
+ if (Utils.isNotEmptyString(this.configurationFile) && fs.existsSync(this.configurationFile)) {
try {
if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
configuration = this.sharedLRUCache.getChargingStationConfiguration(
this.configurationFileHash = configuration.configurationHash;
}
} catch (error) {
- FileUtils.handleFileException(
+ ErrorUtils.handleFileException(
this.configurationFile,
FileType.ChargingStationConfiguration,
error as NodeJS.ErrnoException,
return configuration;
}
- private saveChargingStationAutomaticTransactionGeneratorConfiguration(
- stationTemplate?: ChargingStationTemplate
- ): void {
- this.saveConfiguration({
- automaticTransactionGenerator: (stationTemplate ?? this.getTemplateFromFile())
- .AutomaticTransactionGenerator,
- ...(!Utils.isNullOrUndefined(this.automaticTransactionGenerator?.connectorsStatus) && {
- automaticTransactionGeneratorStatuses: [
- ...this.automaticTransactionGenerator.connectorsStatus.values(),
- ],
- }),
- });
+ private saveChargingStationAutomaticTransactionGeneratorConfiguration(): void {
+ this.saveConfiguration();
}
private saveConnectorsStatus() {
this.saveConfiguration();
}
- private saveConfiguration(
- chargingStationAutomaticTransactionGeneratorConfiguration?: ChargingStationAutomaticTransactionGeneratorConfiguration
- ): void {
- if (this.configurationFile) {
+ private saveConfiguration(): void {
+ if (Utils.isNotEmptyString(this.configurationFile)) {
try {
if (!fs.existsSync(path.dirname(this.configurationFile))) {
fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
}
let configurationData: ChargingStationConfiguration =
- Utils.cloneObject(this.getConfigurationFromFile()) ?? {};
+ Utils.cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()) ?? {};
if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
configurationData.stationInfo = this.stationInfo;
}
if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) {
configurationData.configurationKey = this.ocppConfiguration.configurationKey;
}
- if (chargingStationAutomaticTransactionGeneratorConfiguration) {
- configurationData = merge<ChargingStationConfiguration>(
- configurationData,
- chargingStationAutomaticTransactionGeneratorConfiguration
- );
- }
+ configurationData = merge<ChargingStationConfiguration>(
+ configurationData,
+ buildChargingStationAutomaticTransactionGeneratorConfiguration(this)
+ );
if (this.connectors.size > 0) {
- configurationData.connectorsStatus = [...this.connectors.values()].map(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
- );
+ configurationData.connectorsStatus = buildConnectorsStatus(this);
}
if (this.evses.size > 0) {
- configurationData.evsesStatus = [...this.evses.values()].map((evseStatus) => {
- const status = {
- ...evseStatus,
- connectorsStatus: [...evseStatus.connectors.values()].map(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- ({ transactionSetInterval, ...connectorStatusRest }) => connectorStatusRest
- ),
- };
- delete status.connectors;
- return status as EvseStatusConfiguration;
- });
+ configurationData.evsesStatus = buildEvsesStatus(this);
}
delete configurationData.configurationHash;
const configurationHash = crypto
.update(JSON.stringify(configurationData))
.digest('hex');
if (this.configurationFileHash !== configurationHash) {
- configurationData.configurationHash = configurationHash;
- const measureId = `${FileType.ChargingStationConfiguration} write`;
- const beginId = PerformanceStatistics.beginMeasure(measureId);
- const fileDescriptor = fs.openSync(this.configurationFile, 'w');
- fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
- fs.closeSync(fileDescriptor);
- PerformanceStatistics.endMeasure(measureId, beginId);
- this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
- this.sharedLRUCache.setChargingStationConfiguration(configurationData);
- this.configurationFileHash = configurationHash;
+ AsyncLock.acquire(AsyncLockType.configuration)
+ .then(() => {
+ configurationData.configurationHash = configurationHash;
+ const measureId = `${FileType.ChargingStationConfiguration} write`;
+ const beginId = PerformanceStatistics.beginMeasure(measureId);
+ const fileDescriptor = fs.openSync(this.configurationFile, 'w');
+ fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
+ fs.closeSync(fileDescriptor);
+ PerformanceStatistics.endMeasure(measureId, beginId);
+ this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
+ this.sharedLRUCache.setChargingStationConfiguration(configurationData);
+ this.configurationFileHash = configurationHash;
+ })
+ .catch((error) => {
+ ErrorUtils.handleFileException(
+ this.configurationFile,
+ FileType.ChargingStationConfiguration,
+ error as NodeJS.ErrnoException,
+ this.logPrefix()
+ );
+ })
+ .finally(() => {
+ AsyncLock.release(AsyncLockType.configuration).catch(Constants.EMPTY_FUNCTION);
+ });
} else {
logger.debug(
`${this.logPrefix()} Not saving unchanged charging station configuration file ${
);
}
} catch (error) {
- FileUtils.handleFileException(
+ ErrorUtils.handleFileException(
this.configurationFile,
FileType.ChargingStationConfiguration,
error as NodeJS.ErrnoException,
}
private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
- let configuration: ChargingStationConfiguration | undefined;
- if (this.getOcppPersistentConfiguration() === true) {
- const configurationFromFile = this.getConfigurationFromFile();
- configuration = configurationFromFile?.configurationKey && configurationFromFile;
- }
- if (!Utils.isNullOrUndefined(configuration)) {
- delete configuration.stationInfo;
- delete configuration.connectorsStatus;
- delete configuration.evsesStatus;
- delete configuration.automaticTransactionGenerator;
- delete configuration.automaticTransactionGeneratorStatuses;
- delete configuration.configurationHash;
+ const configurationKey = this.getConfigurationFromFile()?.configurationKey;
+ if (this.getOcppPersistentConfiguration() === true && configurationKey) {
+ return { configurationKey };
}
- return configuration;
+ return undefined;
}
private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined {
skipBufferingOnError: true,
});
if (this.isRegistered() === false) {
- this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
+ this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount;
await Utils.sleep(
this?.bootNotificationResponse?.interval
? this.bootNotificationResponse.interval * 1000
- : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL
+ : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL
);
}
} while (
);
}
if (this.isRegistered() === true) {
- if (this.isInAcceptedState() === true) {
+ if (this.inAcceptedState() === true) {
await this.startMessageSequence();
}
} else {
await OCPPServiceUtils.sendAndSetConnectorStatus(
this,
connectorId,
- connectorBootStatus
+ connectorBootStatus,
+ evseId
);
}
}
OCPPServiceUtils.buildStatusNotificationRequest(
this,
connectorId,
- ConnectorStatusEnum.Unavailable
+ ConnectorStatusEnum.Unavailable,
+ evseId
)
);
delete connectorStatus?.status;
}
private getConfiguredSupervisionUrl(): URL {
+ let configuredSupervisionUrl: string;
const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls();
if (Utils.isNotEmptyArray(supervisionUrls)) {
let configuredSupervisionUrlIndex: number;
configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
break;
}
- return new URL(supervisionUrls[configuredSupervisionUrlIndex]);
+ configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex];
+ } else {
+ configuredSupervisionUrl = supervisionUrls as string;
+ }
+ if (Utils.isNotEmptyString(configuredSupervisionUrl)) {
+ return new URL(configuredSupervisionUrl);
}
- return new URL(supervisionUrls as string);
+ const errorMsg = 'No supervision url(s) configured';
+ logger.error(`${this.logPrefix()} ${errorMsg}`);
+ throw new BaseError(`${errorMsg}`);
}
private stopHeartbeat(): void {
this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() ||
this.getAutoReconnectMaxRetries() === -1
) {
- this.autoReconnectRetryCount++;
+ ++this.autoReconnectRetryCount;
const reconnectDelay = this.getReconnectExponentialDelay()
? Utils.exponentialDelay(this.autoReconnectRetryCount)
: this.getConnectionTimeout() * 1000;