import { BootNotificationResponse, RegistrationStatus } from '../types/ocpp/Responses';
import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration';
import ChargingStationTemplate, { CurrentType, PowerUnits, Voltage } from '../types/ChargingStationTemplate';
+import { Connector, Connectors, SampledValueTemplate } from '../types/Connectors';
import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles } from '../types/ocpp/Configuration';
-import Connectors, { Connector, SampledValueTemplate } from '../types/Connectors';
import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket';
-import WebSocket, { ClientOptions, Data } from 'ws';
+import WebSocket, { ClientOptions, Data, OPEN } from 'ws';
import AutomaticTransactionGenerator from './AutomaticTransactionGenerator';
import { ChargePointStatus } from '../types/ocpp/ChargePointStatus';
public stationInfo!: ChargingStationInfo;
public connectors: Connectors;
public configuration!: ChargingStationConfiguration;
- public hasStopped: boolean;
public wsConnection!: WebSocket;
public requests: Map<string, Request>;
- public messageQueue: string[];
public performanceStatistics!: PerformanceStatistics;
public heartbeatSetInterval!: NodeJS.Timeout;
- public ocppIncomingRequestService!: OCPPIncomingRequestService;
public ocppRequestService!: OCPPRequestService;
private index: number;
private bootNotificationRequest!: BootNotificationRequest;
private bootNotificationResponse!: BootNotificationResponse | null;
private connectorsConfigurationHash!: string;
+ private ocppIncomingRequestService!: OCPPIncomingRequestService;
+ private messageQueue: string[];
private wsConnectionUrl!: URL;
- private hasSocketRestarted: boolean;
+ private wsConnectionRestarted: boolean;
+ private stopped: boolean;
private autoReconnectRetryCount: number;
- private automaticTransactionGeneration!: AutomaticTransactionGenerator;
+ private automaticTransactionGenerator!: AutomaticTransactionGenerator;
private webSocketPingSetInterval!: NodeJS.Timeout;
constructor(index: number, stationTemplateFile: string) {
this.connectors = {} as Connectors;
this.initialize();
- this.hasStopped = false;
- this.hasSocketRestarted = false;
+ this.stopped = false;
+ this.wsConnectionRestarted = false;
this.autoReconnectRetryCount = 0;
this.requests = new Map<string, Request>();
return this.bootNotificationRequest;
}
- public getRandomTagId(): string {
- const index = Math.floor(Math.random() * this.authorizedTags.length);
+ public getRandomIdTag(): string {
+ const index = Math.floor(Utils.secureRandom() * this.authorizedTags.length);
return this.authorizedTags[index];
}
}
public isWebSocketConnectionOpened(): boolean {
- return this.wsConnection?.readyState === WebSocket.OPEN;
+ return this.wsConnection?.readyState === OPEN;
}
public isRegistered(): boolean {
public getSampledValueTemplate(connectorId: number, measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
phase?: MeterValuePhase): SampledValueTemplate | undefined {
if (!Constants.SUPPORTED_MEASURANDS.includes(measurand)) {
- logger.warn(`${this.logPrefix()} Trying to get unsupported MeterValues measurand ${measurand} ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
+ logger.warn(`${this.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
return;
}
if (measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && !this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(measurand)) {
- logger.debug(`${this.logPrefix()} Trying to get MeterValues measurand ${measurand} ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId} not found in '${StandardParametersKey.MeterValuesSampledData}' OCPP parameter`);
+ logger.debug(`${this.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId} not found in '${StandardParametersKey.MeterValuesSampledData}' OCPP parameter`);
return;
}
const sampledValueTemplates: SampledValueTemplate[] = this.getConnector(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)) {
- logger.warn(`${this.logPrefix()} Unsupported MeterValues measurand ${measurand} ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
+ logger.warn(`${this.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
} else if (phase && sampledValueTemplates[index]?.phase === phase && sampledValueTemplates[index]?.measurand === measurand
&& this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData).value.includes(measurand)) {
return sampledValueTemplates[index];
}
}
if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
- const errorMsg = `${this.logPrefix()} Missing MeterValues for default measurand ${measurand} in template on connectorId ${connectorId}`;
+ const errorMsg = `${this.logPrefix()} Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
logger.error(errorMsg);
throw new Error(errorMsg);
}
- logger.debug(`${this.logPrefix()} No MeterValues for measurand ${measurand} ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
+ logger.debug(`${this.logPrefix()} No MeterValues for measurand '${measurand}' ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
}
public getAutomaticTransactionGeneratorRequireAuthorize(): boolean {
this.heartbeatSetInterval = setInterval(async (): Promise<void> => {
await this.ocppRequestService.sendHeartbeat();
}, this.getHeartbeatInterval());
- logger.info(this.logPrefix() + ' Heartbeat started every ' + Utils.milliSecondsToHHMMSS(this.getHeartbeatInterval()));
+ logger.info(this.logPrefix() + ' Heartbeat started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
} else if (this.heartbeatSetInterval) {
- logger.info(this.logPrefix() + ' Heartbeat already started every ' + Utils.milliSecondsToHHMMSS(this.getHeartbeatInterval()));
+ logger.info(this.logPrefix() + ' Heartbeat already started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
} else {
- logger.error(`${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval() ? Utils.milliSecondsToHHMMSS(this.getHeartbeatInterval()) : this.getHeartbeatInterval()}, not starting the heartbeat`);
+ logger.error(`${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval() ? Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()) : this.getHeartbeatInterval()}, not starting the heartbeat`);
}
}
await this.ocppRequestService.sendMeterValues(connectorId, this.getConnector(connectorId).transactionId, interval);
}, interval);
} else {
- logger.error(`${this.logPrefix()} Charging station ${StandardParametersKey.MeterValueSampleInterval} configuration set to ${interval ? Utils.milliSecondsToHHMMSS(interval) : interval}, not sending MeterValues`);
+ logger.error(`${this.logPrefix()} Charging station ${StandardParametersKey.MeterValueSampleInterval} configuration set to ${interval ? Utils.formatDurationMilliSeconds(interval) : interval}, not sending MeterValues`);
}
}
this.performanceStatistics.stop();
}
this.bootNotificationResponse = null;
- this.hasStopped = true;
+ this.stopped = true;
}
public getConfigurationKey(key: string | StandardParametersKey, caseInsensitive = false): ConfigurationKey | undefined {
const stationInfo: ChargingStationInfo = stationTemplateFromFile ?? {} as ChargingStationInfo;
if (!Utils.isEmptyArray(stationTemplateFromFile.power)) {
stationTemplateFromFile.power = stationTemplateFromFile.power as number[];
- const powerArrayRandomIndex = Math.floor(Math.random() * stationTemplateFromFile.power.length);
+ const powerArrayRandomIndex = Math.floor(Utils.secureRandom() * stationTemplateFromFile.power.length);
stationInfo.maxPower = stationTemplateFromFile.powerUnit === PowerUnits.KILO_WATT
? stationTemplateFromFile.power[powerArrayRandomIndex] * 1000
: stationTemplateFromFile.power[powerArrayRandomIndex];
delete this.stationInfo.Connectors;
// Initialize transaction attributes on connectors
for (const connector in this.connectors) {
- if (Utils.convertToInt(connector) > 0 && !this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ if (Utils.convertToInt(connector) > 0 && !this.getConnector(Utils.convertToInt(connector))?.transactionStarted) {
this.initTransactionAttributesOnConnector(Utils.convertToInt(connector));
}
}
}
if (this.isRegistered()) {
await this.startMessageSequence();
- this.hasStopped && (this.hasStopped = false);
- if (this.hasSocketRestarted && this.isWebSocketConnectionOpened()) {
+ this.stopped && (this.stopped = false);
+ if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) {
this.flushMessageQueue();
}
} else {
logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
}
this.autoReconnectRetryCount = 0;
- this.hasSocketRestarted = false;
+ this.wsConnectionRestarted = false;
}
private async onClose(code: number, reason: string): Promise<void> {
private getNumberOfRunningTransactions(): number {
let trxCount = 0;
for (const connector in this.connectors) {
- if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector))?.transactionStarted) {
trxCount++;
}
}
for (const connector in this.connectors) {
if (Utils.convertToInt(connector) === 0) {
continue;
- } else if (!this.hasStopped && !this.getConnector(Utils.convertToInt(connector))?.status && this.getConnector(Utils.convertToInt(connector))?.bootStatus) {
+ } else if (!this.stopped && !this.getConnector(Utils.convertToInt(connector))?.status && this.getConnector(Utils.convertToInt(connector))?.bootStatus) {
// Send status in template at startup
await this.ocppRequestService.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus);
this.getConnector(Utils.convertToInt(connector)).status = this.getConnector(Utils.convertToInt(connector)).bootStatus;
- } else if (this.hasStopped && this.getConnector(Utils.convertToInt(connector))?.bootStatus) {
+ } else if (this.stopped && this.getConnector(Utils.convertToInt(connector))?.bootStatus) {
// Send status in template after reset
await this.ocppRequestService.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus);
this.getConnector(Utils.convertToInt(connector)).status = this.getConnector(Utils.convertToInt(connector)).bootStatus;
- } else if (!this.hasStopped && this.getConnector(Utils.convertToInt(connector))?.status) {
+ } else if (!this.stopped && this.getConnector(Utils.convertToInt(connector))?.status) {
// Send previous status at template reload
await this.ocppRequestService.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).status);
} else {
private startAutomaticTransactionGenerator() {
if (this.stationInfo.AutomaticTransactionGenerator.enable) {
- if (!this.automaticTransactionGeneration) {
- this.automaticTransactionGeneration = new AutomaticTransactionGenerator(this);
+ if (!this.automaticTransactionGenerator) {
+ this.automaticTransactionGenerator = new AutomaticTransactionGenerator(this);
}
- if (this.automaticTransactionGeneration.timeToStop) {
- // The ATG might sleep
- this.automaticTransactionGeneration.start().catch(() => { });
+ if (!this.automaticTransactionGenerator.started) {
+ this.automaticTransactionGenerator.start();
}
}
}
this.stopHeartbeat();
// Stop the ATG
if (this.stationInfo.AutomaticTransactionGenerator.enable &&
- this.automaticTransactionGeneration &&
- !this.automaticTransactionGeneration.timeToStop) {
- await this.automaticTransactionGeneration.stop(reason);
+ this.automaticTransactionGenerator &&
+ this.automaticTransactionGenerator.started) {
+ this.automaticTransactionGenerator.stop();
} else {
for (const connector in this.connectors) {
- if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector))?.transactionStarted) {
const transactionId = this.getConnector(Utils.convertToInt(connector)).transactionId;
await this.ocppRequestService.sendStopTransaction(transactionId, this.getEnergyActiveImportRegisterByTransactionId(transactionId),
this.getTransactionIdTag(transactionId), reason);
if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
this.webSocketPingSetInterval = setInterval(() => {
if (this.isWebSocketConnectionOpened()) {
- this.wsConnection.ping((): void => { });
+ this.wsConnection.ping((): void => { /* This is intentional */ });
}
}, webSocketPingInterval * 1000);
- logger.info(this.logPrefix() + ' WebSocket ping started every ' + Utils.secondsToHHMMSS(webSocketPingInterval));
+ logger.info(this.logPrefix() + ' WebSocket ping started every ' + Utils.formatDurationSeconds(webSocketPingInterval));
} else if (this.webSocketPingSetInterval) {
- logger.info(this.logPrefix() + ' WebSocket ping every ' + Utils.secondsToHHMMSS(webSocketPingInterval) + ' already started');
+ logger.info(this.logPrefix() + ' WebSocket ping every ' + Utils.formatDurationSeconds(webSocketPingInterval) + ' already started');
} else {
- logger.error(`${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval ? Utils.secondsToHHMMSS(webSocketPingInterval) : webSocketPingInterval}, not starting the WebSocket ping`);
+ logger.error(`${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval ? Utils.formatDurationSeconds(webSocketPingInterval) : webSocketPingInterval}, not starting the WebSocket ping`);
}
}
indexUrl = this.index % supervisionUrls.length;
} else {
// Get a random url
- indexUrl = Math.floor(Math.random() * supervisionUrls.length);
+ indexUrl = Math.floor(Utils.secureRandom() * supervisionUrls.length);
}
return new URL(supervisionUrls[indexUrl]);
}
this.initialize();
// Restart the ATG
if (!this.stationInfo.AutomaticTransactionGenerator.enable &&
- this.automaticTransactionGeneration) {
- await this.automaticTransactionGeneration.stop();
+ this.automaticTransactionGenerator) {
+ this.automaticTransactionGenerator.stop();
}
this.startAutomaticTransactionGenerator();
if (this.getEnableStatistics()) {
// Stop the ATG if needed
if (this.stationInfo.AutomaticTransactionGenerator.enable &&
this.stationInfo.AutomaticTransactionGenerator.stopOnConnectionFailure &&
- this.automaticTransactionGeneration &&
- !this.automaticTransactionGeneration.timeToStop) {
- await this.automaticTransactionGeneration.stop();
+ this.automaticTransactionGenerator &&
+ this.automaticTransactionGenerator.started) {
+ this.automaticTransactionGenerator.stop();
}
if (this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries() || this.getAutoReconnectMaxRetries() === -1) {
this.autoReconnectRetryCount++;
const reconnectDelay = (this.getReconnectExponentialDelay() ? Utils.exponentialDelay(this.autoReconnectRetryCount) : this.getConnectionTimeout() * 1000);
- logger.error(`${this.logPrefix()} Socket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectDelay - 100}ms`);
+ const reconnectTimeout = reconnectDelay - 100;
+ logger.error(`${this.logPrefix()} Socket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectTimeout}ms`);
await Utils.sleep(reconnectDelay);
logger.error(this.logPrefix() + ' Socket: reconnecting try #' + this.autoReconnectRetryCount.toString());
- this.openWSConnection({ handshakeTimeout: reconnectDelay - 100 });
- this.hasSocketRestarted = true;
+ this.openWSConnection({ handshakeTimeout: reconnectTimeout }, true);
+ this.wsConnectionRestarted = true;
} else if (this.getAutoReconnectMaxRetries() !== -1) {
logger.error(`${this.logPrefix()} Socket reconnect failure: max retries reached (${this.autoReconnectRetryCount}) or retry disabled (${this.getAutoReconnectMaxRetries()})`);
}