// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
import { createHash } from 'node:crypto';
+import { EventEmitter } from 'node:events';
import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { URL } from 'node:url';
type BootNotificationResponse,
type CachedRequest,
type ChargingStationConfiguration,
+ ChargingStationEvents,
type ChargingStationInfo,
type ChargingStationOcppConfiguration,
type ChargingStationTemplate,
roundTo,
secureRandom,
sleep,
- // watchJsonFile,
+ watchJsonFile,
} from '../utils';
-export class ChargingStation {
+export class ChargingStation extends EventEmitter {
public readonly index: number;
public readonly templateFile: string;
public stationInfo!: ChargingStationInfo;
private reservationExpirationSetInterval?: NodeJS.Timeout;
constructor(index: number, templateFile: string) {
+ super();
this.started = false;
this.starting = false;
this.stopping = false;
this.idTagsCache = IdTagsCache.getInstance();
this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
+ this.on(ChargingStationEvents.started, () => {
+ parentPort?.postMessage(buildStartedMessage(this));
+ });
+ this.on(ChargingStationEvents.stopped, () => {
+ parentPort?.postMessage(buildStoppedMessage(this));
+ });
+ this.on(ChargingStationEvents.updated, () => {
+ parentPort?.postMessage(buildUpdatedMessage(this));
+ });
+
this.initialize();
}
}
this.openWSConnection();
// Monitor charging station template file
- // FIXME: Disabled until the spurious configuration file change detection is identified
- // this.templateFileWatcher = watchJsonFile(
- // this.templateFile,
- // FileType.ChargingStationTemplate,
- // this.logPrefix(),
- // undefined,
- // (event, filename): void => {
- // if (isNotEmptyString(filename) && event === 'change') {
- // try {
- // logger.debug(
- // `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
- // this.templateFile
- // } file have changed, reload`,
- // );
- // this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
- // // Initialize
- // this.initialize();
- // this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)!);
- // // Restart the ATG
- // this.stopAutomaticTransactionGenerator()
- // .then(() => {
- // delete this.automaticTransactionGeneratorConfiguration;
- // if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
- // this.startAutomaticTransactionGenerator();
- // }
- // })
- // .catch((err) =>
- // logger.error(
- // `${this.logPrefix()} failed to stop ATG at ${
- // FileType.ChargingStationTemplate
- // } reload`,
- // err,
- // ),
- // );
- // if (this.getEnableStatistics() === true) {
- // this.performanceStatistics?.restart();
- // } else {
- // this.performanceStatistics?.stop();
- // }
- // // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
- // } catch (error) {
- // logger.error(
- // `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
- // error,
- // );
- // }
- // }
- // },
- // );
+ this.templateFileWatcher = watchJsonFile(
+ this.templateFile,
+ FileType.ChargingStationTemplate,
+ this.logPrefix(),
+ undefined,
+ (event, filename): void => {
+ if (isNotEmptyString(filename) && event === 'change') {
+ try {
+ logger.debug(
+ `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
+ this.templateFile
+ } file have changed, reload`,
+ );
+ this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
+ // Initialize
+ this.initialize();
+ 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?.restart();
+ } else {
+ this.performanceStatistics?.stop();
+ }
+ // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
+ } catch (error) {
+ logger.error(
+ `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
+ error,
+ );
+ }
+ }
+ },
+ );
this.started = true;
- parentPort?.postMessage(buildStartedMessage(this));
+ this.emit(ChargingStationEvents.started);
this.starting = false;
} else {
logger.warn(`${this.logPrefix()} Charging station is already starting...`);
delete this.bootNotificationResponse;
this.started = false;
this.saveConfiguration();
- parentPort?.postMessage(buildStoppedMessage(this));
+ this.emit(ChargingStationEvents.stopped);
this.stopping = false;
} else {
logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
let automaticTransactionGeneratorConfiguration:
| AutomaticTransactionGeneratorConfiguration
| undefined;
- const automaticTransactionGeneratorConfigurationFromFile =
- this.getConfigurationFromFile()?.automaticTransactionGenerator;
+ const stationTemplate = this.getTemplateFromFile();
+ const stationConfiguration = this.getConfigurationFromFile();
if (
this.getAutomaticTransactionGeneratorPersistentConfiguration() &&
- automaticTransactionGeneratorConfigurationFromFile
+ stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+ stationConfiguration?.automaticTransactionGenerator
) {
automaticTransactionGeneratorConfiguration =
- automaticTransactionGeneratorConfigurationFromFile;
+ stationConfiguration?.automaticTransactionGenerator;
} else {
- automaticTransactionGeneratorConfiguration =
- this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+ automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator;
}
this.automaticTransactionGeneratorConfiguration = {
...Constants.DEFAULT_ATG_CONFIGURATION,
this.automaticTransactionGenerator?.start();
}
this.saveAutomaticTransactionGeneratorConfiguration();
- parentPort?.postMessage(buildUpdatedMessage(this));
+ this.emit(ChargingStationEvents.updated);
}
public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
this.automaticTransactionGenerator?.stop();
}
this.saveAutomaticTransactionGeneratorConfiguration();
- parentPort?.postMessage(buildUpdatedMessage(this));
+ this.emit(ChargingStationEvents.updated);
}
public async stopTransactionOnConnector(
dirname(this.templateFile.replace('station-templates', 'configurations')),
`${getHashId(this.index, stationTemplate)}.json`,
);
- const chargingStationConfiguration = this.getConfigurationFromFile();
+ const stationConfiguration = this.getConfigurationFromFile();
if (
- chargingStationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+ stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- (chargingStationConfiguration?.connectorsStatus || chargingStationConfiguration?.evsesStatus)
+ (stationConfiguration?.connectorsStatus || stationConfiguration?.evsesStatus)
) {
- this.initializeConnectorsOrEvsesFromFile(chargingStationConfiguration);
+ this.initializeConnectorsOrEvsesFromFile(stationConfiguration);
} else {
this.initializeConnectorsOrEvsesFromTemplate(stationTemplate);
}
);
}
if (this.isRegistered() === true) {
+ this.emit(ChargingStationEvents.registered);
if (this.inAcceptedState() === true) {
+ this.emit(ChargingStationEvents.accepted);
await this.startMessageSequence();
}
} else {
}
this.wsConnectionRestarted = false;
this.autoReconnectRetryCount = 0;
- parentPort?.postMessage(buildUpdatedMessage(this));
+ this.emit(ChargingStationEvents.updated);
} else {
logger.warn(
`${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`,
this.started === true && (await this.reconnect());
break;
}
- parentPort?.postMessage(buildUpdatedMessage(this));
+ this.emit(ChargingStationEvents.updated);
}
private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
logger.error(`${this.logPrefix()} ${errorMsg}`);
throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg);
}
- parentPort?.postMessage(buildUpdatedMessage(this));
+ this.emit(ChargingStationEvents.updated);
} else {
throw new OCPPError(
ErrorType.PROTOCOL_ERROR,
}
// Start the ATG
- if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
+ if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) {
this.startAutomaticTransactionGenerator();
}
this.wsConnectionRestarted === true && this.flushMessageBuffer();
private async stopMessageSequence(
reason?: StopTransactionReason,
- stopTransactions = true,
+ stopTransactions = this.stationInfo?.stopTransactionsOnStopped ?? true,
): Promise<void> {
// Stop WebSocket ping
this.stopWebSocketPing();