// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
import { createHash } from 'node:crypto';
-import {
- type FSWatcher,
- closeSync,
- existsSync,
- mkdirSync,
- openSync,
- readFileSync,
- writeFileSync,
-} from 'node:fs';
+import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { URL } from 'node:url';
import { parentPort } from 'node:worker_threads';
+import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns';
import merge from 'just-merge';
import { type RawData, WebSocket } from 'ws';
deleteConfigurationKey,
getConfigurationKey,
setConfigurationKeyValue,
-} from './ChargingStationConfigurationUtils';
+} from './ConfigurationKeyUtils';
import {
buildConnectorsMap,
+ checkChargingStation,
checkConnectorsConfiguration,
checkStationInfoConnectorStatus,
checkTemplate,
- countReservableConnectors,
createBootNotificationRequest,
createSerialNumber,
getAmperageLimitationUnitDivider,
getHashId,
getIdTagsFile,
getMaxNumberOfEvses,
+ getNumberOfReservableConnectors,
getPhaseRotationValue,
+ hasFeatureProfile,
+ hasReservationExpired,
initializeConnectorsMapStatus,
propagateSerialNumber,
+ removeExpiredReservations,
stationTemplateToStationInfo,
warnTemplateKeysDeprecation,
-} from './ChargingStationUtils';
+} from './Helpers';
import { IdTagsCache } from './IdTagsCache';
import {
OCPP16IncomingRequestService,
RegistrationStatusEnumType,
RequestCommand,
type Reservation,
- ReservationFilterKey,
+ type ReservationKey,
ReservationTerminationReason,
type Response,
StandardParametersKey,
isUndefined,
logPrefix,
logger,
+ min,
+ once,
roundTo,
secureRandom,
sleep,
public idTagsCache: IdTagsCache;
public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
- public wsConnection!: WebSocket | null;
+ public wsConnection: WebSocket | null;
public readonly connectors: Map<number, ConnectorStatus>;
public readonly evses: Map<number, EvseStatus>;
public readonly requests: Map<string, CachedRequest>;
private configurationFileHash!: string;
private connectorsConfigurationHash!: string;
private evsesConfigurationHash!: string;
+ private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration;
private ocppIncomingRequestService!: OCPPIncomingRequestService;
private readonly messageBuffer: Set<string>;
private configuredSupervisionUrl!: URL;
this.started = false;
this.starting = false;
this.stopping = false;
+ this.wsConnection = null;
this.wsConnectionRestarted = false;
this.autoReconnectRetryCount = 0;
this.index = index;
return this.stationInfo.enableStatistics ?? false;
}
- public getMustAuthorizeAtRemoteStart(): boolean {
- return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
+ public getRemoteAuthorization(): boolean {
+ return this.stationInfo.remoteAuthorization ?? true;
}
public getNumberOfPhases(stationInfo?: ChargingStationInfo): number {
}
public getMaximumPower(stationInfo?: ChargingStationInfo): number {
- const localStationInfo = stationInfo ?? this.stationInfo;
- // eslint-disable-next-line @typescript-eslint/dot-notation
- return (
- (localStationInfo['maxPower' as keyof ChargingStationInfo] as number) ??
- localStationInfo.maximumPower
- );
+ return (stationInfo ?? this.stationInfo).maximumPower!;
}
public getConnectorMaximumAvailablePower(connectorId: number): number {
const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
const connectorChargingProfilesPowerLimit =
getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
- return Math.min(
+ return min(
isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower,
isNaN(connectorAmperageLimitationPowerLimit!)
? Infinity
}
public getNumberOfRunningTransactions(): number {
- let trxCount = 0;
+ let numberOfRunningTransactions = 0;
if (this.hasEvses) {
for (const [evseId, evseStatus] of this.evses) {
if (evseId === 0) {
}
for (const connectorStatus of evseStatus.connectors.values()) {
if (connectorStatus.transactionStarted === true) {
- ++trxCount;
+ ++numberOfRunningTransactions;
}
}
}
} else {
for (const connectorId of this.connectors.keys()) {
if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
- ++trxCount;
+ ++numberOfRunningTransactions;
}
}
}
- return trxCount;
+ return numberOfRunningTransactions;
}
public getOutOfOrderEndMeterValues(): boolean {
public getHeartbeatInterval(): number {
const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval);
if (HeartbeatInterval) {
- return convertToInt(HeartbeatInterval.value) * 1000;
+ return secondsToMilliseconds(convertToInt(HeartbeatInterval.value));
}
const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval);
if (HeartBeatInterval) {
- return convertToInt(HeartBeatInterval.value) * 1000;
+ return secondsToMilliseconds(convertToInt(HeartBeatInterval.value));
}
this.stationInfo?.autoRegister === false &&
logger.warn(
);
} else {
logger.error(
- `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()},
- not starting the heartbeat`,
+ `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat`,
);
}
}
public startMeterValues(connectorId: number, interval: number): void {
if (connectorId === 0) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()}`,
+ `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}`,
);
return;
}
if (!this.getConnectorStatus(connectorId)) {
logger.error(
`${this.logPrefix()} Trying to start MeterValues on non existing connector id
- ${connectorId.toString()}`,
+ ${connectorId}`,
);
return;
}
if (this.getConnectorStatus(connectorId)?.transactionStarted === false) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}
- with no transaction started`,
+ `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started`,
);
return;
} else if (
isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId)
) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}
- with no transaction id`,
+ `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id`,
);
return;
}
if (this.getEnableStatistics() === true) {
this.performanceStatistics?.start();
}
- if (this.hasFeatureProfile(SupportedFeatureProfiles.Reservation)) {
+ if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
this.startReservationExpirationSetInterval();
}
this.openWSConnection();
this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)!);
// Restart the ATG
this.stopAutomaticTransactionGenerator();
+ delete this.automaticTransactionGeneratorConfiguration;
if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
this.startAutomaticTransactionGenerator();
}
if (this.getEnableStatistics() === true) {
this.performanceStatistics?.stop();
}
+ if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
+ this.stopReservationExpirationSetInterval();
+ }
this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
this.templateFileWatcher?.close();
this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
}
}
- public hasFeatureProfile(featureProfile: SupportedFeatureProfiles): boolean | undefined {
- return getConfigurationKey(
- this,
- StandardParametersKey.SupportedFeatureProfiles,
- )?.value?.includes(featureProfile);
- }
-
public bufferMessage(message: string): void {
this.messageBuffer.add(message);
}
public openWSConnection(
- options: WsOptions = this.stationInfo?.wsOptions ?? {},
- params: { closeOpened?: boolean; terminateOpened?: boolean } = {
- closeOpened: false,
- terminateOpened: false,
- },
+ options?: WsOptions,
+ params?: { closeOpened?: boolean; terminateOpened?: boolean },
): void {
- options = { handshakeTimeout: this.getConnectionTimeout() * 1000, ...options };
+ options = {
+ handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()),
+ ...this.stationInfo?.wsOptions,
+ ...options,
+ };
params = { ...{ closeOpened: false, terminateOpened: false }, ...params };
- if (this.started === false && this.starting === false) {
- logger.warn(
- `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()}
- on stopped charging station`,
- );
+ if (!checkChargingStation(this, this.logPrefix())) {
return;
}
if (
if (this.isWebSocketConnectionOpened() === true) {
logger.warn(
- `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()}
- is already opened`,
+ `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened`,
);
return;
}
}
public getAutomaticTransactionGeneratorConfiguration(): AutomaticTransactionGeneratorConfiguration {
- let automaticTransactionGeneratorConfiguration:
- | AutomaticTransactionGeneratorConfiguration
- | undefined;
- const automaticTransactionGeneratorConfigurationFromFile =
- this.getConfigurationFromFile()?.automaticTransactionGenerator;
- if (
- this.getAutomaticTransactionGeneratorPersistentConfiguration() &&
- automaticTransactionGeneratorConfigurationFromFile
- ) {
- automaticTransactionGeneratorConfiguration =
- automaticTransactionGeneratorConfigurationFromFile;
- } else {
- automaticTransactionGeneratorConfiguration =
- this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+ if (isNullOrUndefined(this.automaticTransactionGeneratorConfiguration)) {
+ let automaticTransactionGeneratorConfiguration:
+ | AutomaticTransactionGeneratorConfiguration
+ | undefined;
+ const automaticTransactionGeneratorConfigurationFromFile =
+ this.getConfigurationFromFile()?.automaticTransactionGenerator;
+ if (
+ this.getAutomaticTransactionGeneratorPersistentConfiguration() &&
+ automaticTransactionGeneratorConfigurationFromFile
+ ) {
+ automaticTransactionGeneratorConfiguration =
+ automaticTransactionGeneratorConfigurationFromFile;
+ } else {
+ automaticTransactionGeneratorConfiguration =
+ this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+ }
+ this.automaticTransactionGeneratorConfiguration = {
+ ...Constants.DEFAULT_ATG_CONFIGURATION,
+ ...automaticTransactionGeneratorConfiguration,
+ };
}
- return {
- ...Constants.DEFAULT_ATG_CONFIGURATION,
- ...automaticTransactionGeneratorConfiguration,
- };
+ return this.automaticTransactionGeneratorConfiguration!;
}
public getAutomaticTransactionGeneratorStatuses(): Status[] | undefined {
);
}
- public getReservationOnConnectorId0Enabled(): boolean {
+ public getReserveConnectorZeroSupported(): boolean {
return convertToBoolean(
getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value,
);
}
public async addReservation(reservation: Reservation): Promise<void> {
- const [exists, reservationFound] = this.doesReservationExists(reservation);
- if (exists) {
+ const reservationFound = this.getReservationBy('reservationId', reservation.reservationId);
+ if (!isUndefined(reservationFound)) {
await this.removeReservation(
reservationFound!,
ReservationTerminationReason.REPLACE_EXISTING,
public async removeReservation(
reservation: Reservation,
- reason?: ReservationTerminationReason,
+ reason: ReservationTerminationReason,
): Promise<void> {
const connector = this.getConnectorStatus(reservation.connectorId)!;
switch (reason) {
case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
- delete connector.reservation;
- break;
case ReservationTerminationReason.TRANSACTION_STARTED:
delete connector.reservation;
break;
delete connector.reservation;
break;
default:
- break;
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ throw new BaseError(`Unknown reservation termination reason '${reason}'`);
}
}
public getReservationBy(
- filterKey: ReservationFilterKey,
+ filterKey: ReservationKey,
value: number | string,
): Reservation | undefined {
if (this.hasEvses) {
for (const evseStatus of this.evses.values()) {
for (const connectorStatus of evseStatus.connectors.values()) {
- if (connectorStatus?.reservation?.[filterKey as keyof Reservation] === value) {
+ if (connectorStatus?.reservation?.[filterKey] === value) {
return connectorStatus.reservation;
}
}
}
} else {
for (const connectorStatus of this.connectors.values()) {
- if (connectorStatus?.reservation?.[filterKey as keyof Reservation] === value) {
+ if (connectorStatus?.reservation?.[filterKey] === value) {
return connectorStatus.reservation;
}
}
}
}
- public doesReservationExists(
- reservation: Partial<Reservation>,
- ): [boolean, Reservation | undefined] {
- const foundReservation = this.getReservationBy(
- ReservationFilterKey.RESERVATION_ID,
- reservation.id!,
- );
- return isUndefined(foundReservation) ? [false, undefined] : [true, foundReservation];
+ public isConnectorReservable(
+ reservationId: number,
+ idTag?: string,
+ connectorId?: number,
+ ): boolean {
+ const reservation = this.getReservationBy('reservationId', reservationId);
+ const reservationExists = !isUndefined(reservation) && !hasReservationExpired(reservation!);
+ if (arguments.length === 1) {
+ return !reservationExists;
+ } else if (arguments.length > 1) {
+ const userReservation = !isUndefined(idTag)
+ ? this.getReservationBy('idTag', idTag!)
+ : undefined;
+ const userReservationExists =
+ !isUndefined(userReservation) && !hasReservationExpired(userReservation!);
+ const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0;
+ const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
+ return (
+ !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
+ );
+ }
+ return false;
}
- public startReservationExpirationSetInterval(customInterval?: number): void {
- const interval =
- customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL;
- logger.info(
- `${this.logPrefix()} Reservation expiration date interval is set to ${interval}
- and starts on charging station now`,
- );
+ private startReservationExpirationSetInterval(customInterval?: number): void {
+ const interval = customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_INTERVAL;
if (interval > 0) {
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
- this.reservationExpirationSetInterval = setInterval(async (): Promise<void> => {
- const now = new Date();
- if (this.hasEvses) {
- for (const evseStatus of this.evses.values()) {
- for (const connectorStatus of evseStatus.connectors.values()) {
- if (connectorStatus.reservation && connectorStatus.reservation.expiryDate < now) {
- await this.removeReservation(
- connectorStatus.reservation,
- ReservationTerminationReason.EXPIRED,
- );
- }
- }
- }
- } else {
- for (const connectorStatus of this.connectors.values()) {
- if (connectorStatus.reservation && connectorStatus.reservation.expiryDate < now) {
- await this.removeReservation(
- connectorStatus.reservation,
- ReservationTerminationReason.EXPIRED,
- );
- }
- }
- }
+ logger.info(
+ `${this.logPrefix()} Reservation expiration date checks started every ${formatDurationMilliSeconds(
+ interval,
+ )}`,
+ );
+ this.reservationExpirationSetInterval = setInterval((): void => {
+ removeExpiredReservations(this).catch(Constants.EMPTY_FUNCTION);
}, interval);
}
}
- public restartReservationExpiryDateSetInterval(): void {
- this.stopReservationExpirationSetInterval();
- this.startReservationExpirationSetInterval();
- }
-
- public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean {
- return this.getReservationBy(ReservationFilterKey.CONNECTOR_ID, connectorId)?.idTag === idTag;
- }
-
- public isConnectorReservable(
- reservationId: number,
- idTag?: string,
- connectorId?: number,
- ): boolean {
- const [alreadyExists] = this.doesReservationExists({ id: reservationId });
- if (alreadyExists) {
- return alreadyExists;
+ private stopReservationExpirationSetInterval(): void {
+ if (!isNullOrUndefined(this.reservationExpirationSetInterval)) {
+ clearInterval(this.reservationExpirationSetInterval);
}
- const userReservedAlready = isUndefined(
- this.getReservationBy(ReservationFilterKey.ID_TAG, idTag!),
- )
- ? false
- : true;
- const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0;
- const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
- return !alreadyExists && !userReservedAlready && notConnectorZero && freeConnectorsAvailable;
}
+ // private restartReservationExpiryDateSetInterval(): void {
+ // this.stopReservationExpirationSetInterval();
+ // this.startReservationExpirationSetInterval();
+ // }
+
private getNumberOfReservableConnectors(): number {
- let reservableConnectors = 0;
+ let numberOfReservableConnectors = 0;
if (this.hasEvses) {
for (const evseStatus of this.evses.values()) {
- reservableConnectors += countReservableConnectors(evseStatus.connectors);
+ numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors);
}
} else {
- reservableConnectors = countReservableConnectors(this.connectors);
+ numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors);
}
- return reservableConnectors - this.getNumberOfReservationsOnConnectorZero();
+ return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero();
}
private getNumberOfReservationsOnConnectorZero(): number {
- let numberOfReservations = 0;
- if (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation) {
- ++numberOfReservations;
- } else if (this.connectors.get(0)?.reservation) {
- ++numberOfReservations;
+ if (
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation) ||
+ (!this.hasEvses && this.connectors.get(0)?.reservation)
+ ) {
+ return 1;
}
- return numberOfReservations;
+ return 0;
}
private flushMessageBuffer(): void {
return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
}
- private stopReservationExpirationSetInterval(): void {
- if (this.reservationExpirationSetInterval) {
- clearInterval(this.reservationExpirationSetInterval);
- }
- }
-
private getSupervisionUrlOcppKey(): string {
return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl;
}
private getStationInfoFromTemplate(): ChargingStationInfo {
const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile()!;
checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
- warnTemplateKeysDeprecation(stationTemplate, this.logPrefix(), this.templateFile);
+ const warnTemplateKeysDeprecationOnce = once(warnTemplateKeysDeprecation, this);
+ warnTemplateKeysDeprecationOnce(stationTemplate, this.logPrefix(), this.templateFile);
if (stationTemplate?.Connectors) {
checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile);
}
stationTemplate?.firmwareUpgrade ?? {},
);
stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime)
- ? stationTemplate.resetTime! * 1000
+ ? secondsToMilliseconds(stationTemplate.resetTime!)
: Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
return stationInfo;
}
private handleUnsupportedVersion(version: OCPPVersion) {
- const errorMsg = `Unsupported protocol version '${version}' configured
- in template file ${this.templateFile}`;
+ const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
logger.error(`${this.logPrefix()} ${errorMsg}`);
throw new BaseError(errorMsg);
}
if (this.stationInfo?.autoRegister === true) {
this.bootNotificationResponse = {
currentTime: new Date(),
- interval: this.getHeartbeatInterval() / 1000,
+ interval: millisecondsToSeconds(this.getHeartbeatInterval()),
status: RegistrationStatusEnumType.ACCEPTED,
};
}
for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
if (
connectorId === 0 &&
- (!stationTemplate?.Connectors[connectorId] ||
+ (!stationTemplate?.Connectors?.[connectorId] ||
this.getUseConnectorId0(stationTemplate) === false)
) {
continue;
} with evse id 0 with no connector id 0 configuration`,
);
}
+ if (Object.keys(stationTemplate?.Evses?.[0]?.Connectors as object).length > 1) {
+ logger.warn(
+ `${this.logPrefix()} Charging station information from template ${
+ this.templateFile
+ } with evse id 0 with more than one connector configuration, only connector id 0 configuration will be used`,
+ );
+ }
if (stationTemplate?.Evses) {
const evsesConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
.update(JSON.stringify(stationTemplate?.Evses))
this.evsesConfigurationHash = evsesConfigHash;
const templateMaxEvses = getMaxNumberOfEvses(stationTemplate?.Evses);
if (templateMaxEvses > 0) {
- for (const evse in stationTemplate.Evses) {
- const evseId = convertToInt(evse);
+ for (const evseKey in stationTemplate.Evses) {
+ const evseId = convertToInt(evseKey);
this.evses.set(evseId, {
connectors: buildConnectorsMap(
- stationTemplate?.Evses[evse]?.Connectors,
+ stationTemplate?.Evses[evseKey]?.Connectors,
this.logPrefix(),
this.templateFile,
),
if (!existsSync(dirname(this.configurationFile))) {
mkdirSync(dirname(this.configurationFile), { recursive: true });
}
- let configurationData: ChargingStationConfiguration =
- cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()!) ?? {};
+ let configurationData: ChargingStationConfiguration = this.getConfigurationFromFile()
+ ? cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()!)
+ : {};
if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
configurationData.stationInfo = this.stationInfo;
} else {
)
.digest('hex');
if (this.configurationFileHash !== configurationHash) {
- AsyncLock.acquire(AsyncLockType.configuration)
- .then(() => {
- configurationData.configurationHash = configurationHash;
- const measureId = `${FileType.ChargingStationConfiguration} write`;
- const beginId = PerformanceStatistics.beginMeasure(measureId);
- const fileDescriptor = openSync(this.configurationFile, 'w');
- writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
- closeSync(fileDescriptor);
- PerformanceStatistics.endMeasure(measureId, beginId);
- this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
- this.sharedLRUCache.setChargingStationConfiguration(configurationData);
- this.configurationFileHash = configurationHash;
- })
- .catch((error) => {
- handleFileException(
- this.configurationFile,
- FileType.ChargingStationConfiguration,
- error as NodeJS.ErrnoException,
- this.logPrefix(),
- );
- })
- .finally(() => {
- AsyncLock.release(AsyncLockType.configuration).catch(Constants.EMPTY_FUNCTION);
- });
+ AsyncLock.runExclusive(AsyncLockType.configuration, () => {
+ configurationData.configurationHash = configurationHash;
+ const measureId = `${FileType.ChargingStationConfiguration} write`;
+ const beginId = PerformanceStatistics.beginMeasure(measureId);
+ writeFileSync(
+ this.configurationFile,
+ JSON.stringify(configurationData, undefined, 2),
+ 'utf8',
+ );
+ PerformanceStatistics.endMeasure(measureId, beginId);
+ this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
+ this.sharedLRUCache.setChargingStationConfiguration(configurationData);
+ this.configurationFileHash = configurationHash;
+ }).catch((error) => {
+ handleFileException(
+ this.configurationFile,
+ FileType.ChargingStationConfiguration,
+ error as NodeJS.ErrnoException,
+ this.logPrefix(),
+ );
+ });
} else {
logger.debug(
`${this.logPrefix()} Not saving unchanged charging station configuration file ${
this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount;
await sleep(
this?.bootNotificationResponse?.interval
- ? this.bootNotificationResponse.interval * 1000
+ ? secondsToMilliseconds(this.bootNotificationResponse.interval)
: Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL,
);
}
// -1 for unlimited, 0 for disabling
private getAutoReconnectMaxRetries(): number | undefined {
- return (
- this.stationInfo.autoReconnectMaxRetries ?? Configuration.getAutoReconnectMaxRetries() ?? -1
- );
+ return this.stationInfo.autoReconnectMaxRetries ?? -1;
}
- // 0 for disabling
+ // -1 for unlimited, 0 for disabling
private getRegistrationMaxRetries(): number | undefined {
return this.stationInfo.registrationMaxRetries ?? -1;
}
if (this.isWebSocketConnectionOpened() === true) {
this.wsConnection?.ping();
}
- }, webSocketPingInterval * 1000);
+ }, secondsToMilliseconds(webSocketPingInterval));
logger.info(
`${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
webSocketPingInterval,
++this.autoReconnectRetryCount;
const reconnectDelay = this.getReconnectExponentialDelay()
? exponentialDelay(this.autoReconnectRetryCount)
- : this.getConnectionTimeout() * 1000;
+ : secondsToMilliseconds(this.getConnectionTimeout());
const reconnectDelayWithdraw = 1000;
const reconnectTimeout =
reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0
);
this.openWSConnection(
{
- ...(this.stationInfo?.wsOptions ?? {}),
handshakeTimeout: reconnectTimeout,
},
{ closeOpened: true },