import Utils from '../utils/Utils';
import crypto from 'crypto';
import fs from 'fs';
-import getLogger from '../utils/Logger';
+import logger from '../utils/Logger';
import { parentPort } from 'worker_threads';
import path from 'path';
export default class ChargingStation {
+ public readonly id: string;
public readonly stationTemplateFile: string;
public authorizedTags: string[];
public stationInfo!: ChargingStationInfo;
public performanceStatistics!: PerformanceStatistics;
public heartbeatSetInterval!: NodeJS.Timeout;
public ocppRequestService!: OCPPRequestService;
- private readonly id: string;
private readonly index: number;
private bootNotificationRequest!: BootNotificationRequest;
private bootNotificationResponse!: BootNotificationResponse | null;
this.id = Utils.generateUUID();
this.index = index;
this.stationTemplateFile = stationTemplateFile;
- this.connectors = new Map<number, ConnectorStatus>();
- this.initialize();
-
this.stopped = false;
this.wsConnectionRestarted = false;
this.autoReconnectRetryCount = 0;
-
+ this.connectors = new Map<number, ConnectorStatus>();
this.requests = new Map<string, CachedRequest>();
this.messageBuffer = new Set<string>();
-
+ this.initialize();
this.authorizedTags = this.getAuthorizedTags();
}
}
public isConnectorAvailable(id: number): boolean {
- return this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE;
+ return id > 0 && this.getConnectorStatus(id).availability === AvailabilityType.OPERATIVE;
}
public getNumberOfConnectors(): number {
defaultVoltageOut = Voltage.VOLTAGE_400;
break;
default:
- getLogger().error(errMsg);
+ logger.error(errMsg);
throw new Error(errMsg);
}
return !Utils.isUndefined(this.stationInfo.voltageOut) ? this.stationInfo.voltageOut : defaultVoltageOut;
public getSampledValueTemplate(connectorId: number, measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
phase?: MeterValuePhase): SampledValueTemplate | undefined {
if (!Constants.SUPPORTED_MEASURANDS.includes(measurand)) {
- getLogger().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)) {
- getLogger().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.getConnectorStatus(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)) {
- getLogger().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}`;
- getLogger().error(errorMsg);
+ logger.error(errorMsg);
throw new Error(errorMsg);
}
- getLogger().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());
- getLogger().info(this.logPrefix() + ' Heartbeat started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
+ logger.info(this.logPrefix() + ' Heartbeat started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
} else if (this.heartbeatSetInterval) {
- getLogger().info(this.logPrefix() + ' Heartbeat already started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
+ logger.info(this.logPrefix() + ' Heartbeat already started every ' + Utils.formatDurationMilliSeconds(this.getHeartbeatInterval()));
} else {
- getLogger().error(`${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval() ? Utils.formatDurationMilliSeconds(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`);
}
}
public startMeterValues(connectorId: number, interval: number): void {
if (connectorId === 0) {
- getLogger().error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`);
+ logger.error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`);
return;
}
if (!this.getConnectorStatus(connectorId)) {
- getLogger().error(`${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`);
+ logger.error(`${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`);
return;
}
if (!this.getConnectorStatus(connectorId)?.transactionStarted) {
- getLogger().error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`);
+ logger.error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`);
return;
} else if (this.getConnectorStatus(connectorId)?.transactionStarted && !this.getConnectorStatus(connectorId)?.transactionId) {
- getLogger().error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`);
+ logger.error(`${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`);
return;
}
if (interval > 0) {
await this.ocppRequestService.sendMeterValues(connectorId, this.getConnectorStatus(connectorId).transactionId, interval);
}, interval);
} else {
- getLogger().error(`${this.logPrefix()} Charging station ${StandardParametersKey.MeterValueSampleInterval} configuration set to ${interval ? Utils.formatDurationMilliSeconds(interval) : interval}, not sending MeterValues`);
+ logger.error(`${this.logPrefix()} Charging station ${StandardParametersKey.MeterValueSampleInterval} configuration set to ${interval ? Utils.formatDurationMilliSeconds(interval) : interval}, not sending MeterValues`);
}
}
reboot,
});
} else {
- getLogger().error(`${this.logPrefix()} Trying to add an already existing configuration key: %j`, keyFound);
+ logger.error(`${this.logPrefix()} Trying to add an already existing configuration key: %j`, keyFound);
}
}
const keyIndex = this.configuration.configurationKey.indexOf(keyFound);
this.configuration.configurationKey[keyIndex].value = value;
} else {
- getLogger().error(`${this.logPrefix()} Trying to set a value on a non existing configuration key: %j`, { key, value });
+ logger.error(`${this.logPrefix()} Trying to set a value on a non existing configuration key: %j`, { key, value });
}
}
private handleUnsupportedVersion(version: OCPPVersion) {
const errMsg = `${this.logPrefix()} Unsupported protocol version '${version}' configured in template file ${this.stationTemplateFile}`;
- getLogger().error(errMsg);
+ logger.error(errMsg);
throw new Error(errMsg);
}
// Build connectors if needed
const maxConnectors = this.getMaxNumberOfConnectors();
if (maxConnectors <= 0) {
- getLogger().warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with ${maxConnectors} connectors`);
+ logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with ${maxConnectors} connectors`);
}
const templateMaxConnectors = this.getTemplateMaxNumberOfConnectors();
if (templateMaxConnectors <= 0) {
- getLogger().warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector configuration`);
+ logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector configuration`);
}
if (!this.stationInfo.Connectors[0]) {
- getLogger().warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector Id 0 configuration`);
+ logger.warn(`${this.logPrefix()} Charging station template ${this.stationTemplateFile} with no connector Id 0 configuration`);
}
// Sanity check
if (maxConnectors > (this.stationInfo.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) && !this.stationInfo.randomConnectors) {
- getLogger().warn(`${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${this.stationTemplateFile}, forcing random connector configurations affectation`);
+ logger.warn(`${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${this.stationTemplateFile}, forcing random connector configurations affectation`);
this.stationInfo.randomConnectors = true;
}
const connectorsConfigHash = crypto.createHash('sha256').update(JSON.stringify(this.stationInfo.Connectors) + maxConnectors.toString()).digest('hex');
this.wsConfiguredConnectionUrl = new URL(this.getConfiguredSupervisionUrl().href + '/' + this.stationInfo.chargingStationId);
switch (this.getOcppVersion()) {
case OCPPVersion.VERSION_16:
- this.ocppIncomingRequestService = new OCPP16IncomingRequestService(this);
- this.ocppRequestService = new OCPP16RequestService(this, new OCPP16ResponseService(this));
+ this.ocppIncomingRequestService = OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>(this);
+ this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(this, OCPP16ResponseService.getInstance<OCPP16ResponseService>(this));
break;
default:
this.handleUnsupportedVersion(this.getOcppVersion());
}
this.stationInfo.powerDivider = this.getPowerDivider();
if (this.getEnableStatistics()) {
- this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId, this.wsConnectionUrl);
+ this.performanceStatistics = PerformanceStatistics.getInstance(this.id, this.stationInfo.chargingStationId, this.wsConnectionUrl);
}
}
}
private async onOpen(): Promise<void> {
- getLogger().info(`${this.logPrefix()} Connected to OCPP server through ${this.wsConnectionUrl.toString()}`);
+ logger.info(`${this.logPrefix()} Connected to OCPP server through ${this.wsConnectionUrl.toString()}`);
if (!this.isInAcceptedState()) {
// Send BootNotification
let registrationRetryCount = 0;
this.flushMessageBuffer();
}
} else {
- getLogger().error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
+ logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
}
this.autoReconnectRetryCount = 0;
this.wsConnectionRestarted = false;
// Normal close
case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
- getLogger().info(`${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
+ logger.info(`${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
this.autoReconnectRetryCount = 0;
break;
// Abnormal close
default:
- getLogger().error(`${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
+ logger.error(`${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(code)}' and reason '${reason}'`);
await this.reconnect(code);
break;
}
// Error
default:
errMsg = `${this.logPrefix()} Wrong message type ${messageType}`;
- getLogger().error(errMsg);
+ logger.error(errMsg);
throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
}
} catch (error) {
// Log
- getLogger().error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data.toString(), this.requests.get(messageId), error);
+ logger.error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data.toString(), this.requests.get(messageId), error);
// Send error
messageType === MessageType.CALL_MESSAGE && await this.ocppRequestService.sendError(messageId, error as OCPPError, commandName);
}
}
private onPing(): void {
- getLogger().debug(this.logPrefix() + ' Received a WS ping (rfc6455) from the server');
+ logger.debug(this.logPrefix() + ' Received a WS ping (rfc6455) from the server');
}
private onPong(): void {
- getLogger().debug(this.logPrefix() + ' Received a WS pong (rfc6455) from the server');
+ logger.debug(this.logPrefix() + ' Received a WS pong (rfc6455) from the server');
}
private async onError(error: WSError): Promise<void> {
- getLogger().error(this.logPrefix() + ' WebSocket error: %j', error);
+ logger.error(this.logPrefix() + ' WebSocket error: %j', error);
// switch (error.code) {
// case 'ECONNREFUSED':
// await this.reconnect(error);
FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error as NodeJS.ErrnoException);
}
} else {
- getLogger().info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile);
+ logger.info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile);
}
return authorizedTags;
}
this.stopHeartbeat();
// Stop the ATG
if (this.stationInfo.AutomaticTransactionGenerator.enable &&
- this.automaticTransactionGenerator &&
- this.automaticTransactionGenerator.started) {
+ this.automaticTransactionGenerator?.started) {
this.automaticTransactionGenerator.stop();
} else {
for (const connectorId of this.connectors.keys()) {
this.wsConnection.ping((): void => { /* This is intentional */ });
}
}, webSocketPingInterval * 1000);
- getLogger().info(this.logPrefix() + ' WebSocket ping started every ' + Utils.formatDurationSeconds(webSocketPingInterval));
+ logger.info(this.logPrefix() + ' WebSocket ping started every ' + Utils.formatDurationSeconds(webSocketPingInterval));
} else if (this.webSocketPingSetInterval) {
- getLogger().info(this.logPrefix() + ' WebSocket ping every ' + Utils.formatDurationSeconds(webSocketPingInterval) + ' already started');
+ logger.info(this.logPrefix() + ' WebSocket ping every ' + Utils.formatDurationSeconds(webSocketPingInterval) + ' already started');
} else {
- getLogger().error(`${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval ? Utils.formatDurationSeconds(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`);
}
}
private warnDeprecatedTemplateKey(template: ChargingStationTemplate, key: string, chargingStationId: string, logMsgToAppend = ''): void {
if (!Utils.isUndefined(template[key])) {
- getLogger().warn(`${Utils.logPrefix(` ${chargingStationId} |`)} Deprecated template key '${key}' usage in file '${this.stationTemplateFile}'${logMsgToAppend && '. ' + logMsgToAppend}`);
+ logger.warn(`${Utils.logPrefix(` ${chargingStationId} |`)} Deprecated template key '${key}' usage in file '${this.stationTemplateFile}'${logMsgToAppend && '. ' + logMsgToAppend}`);
}
}
if (this.index <= supervisionUrls.length) {
urlIndex = this.index - 1;
} else {
- getLogger().warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`);
+ logger.warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`);
}
break;
default:
- getLogger().error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${SupervisionUrlDistribution.ROUND_ROBIN}`);
+ logger.error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${SupervisionUrlDistribution.ROUND_ROBIN}`);
urlIndex = (this.index - 1) % supervisionUrls.length;
break;
}
if (HeartBeatInterval) {
return Utils.convertToInt(HeartBeatInterval.value) * 1000;
}
- !this.stationInfo.autoRegister && getLogger().warn(`${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL}`);
+ !this.stationInfo.autoRegister && logger.warn(`${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL}`);
return Constants.DEFAULT_HEARTBEAT_INTERVAL;
}
break;
}
this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
- getLogger().info(this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString());
+ logger.info(this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString());
}
private stopMeterValues(connectorId: number) {
fs.watch(authorizationFile, (event, filename) => {
if (filename && event === 'change') {
try {
- getLogger().debug(this.logPrefix() + ' Authorization file ' + authorizationFile + ' have changed, reload');
+ logger.debug(this.logPrefix() + ' Authorization file ' + authorizationFile + ' have changed, reload');
// Initialize authorizedTags
this.authorizedTags = this.getAuthorizedTags();
} catch (error) {
- getLogger().error(this.logPrefix() + ' Authorization file monitoring error: %j', error);
+ logger.error(this.logPrefix() + ' Authorization file monitoring error: %j', error);
}
}
});
FileUtils.handleFileException(this.logPrefix(), 'Authorization', authorizationFile, error as NodeJS.ErrnoException);
}
} else {
- getLogger().info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile + '. Not monitoring changes');
+ logger.info(this.logPrefix() + ' No authorization file given in template file ' + this.stationTemplateFile + '. Not monitoring changes');
}
}
fs.watch(this.stationTemplateFile, (event, filename): void => {
if (filename && event === 'change') {
try {
- getLogger().debug(this.logPrefix() + ' Template file ' + this.stationTemplateFile + ' have changed, reload');
+ logger.debug(this.logPrefix() + ' Template file ' + this.stationTemplateFile + ' have changed, reload');
// Initialize
this.initialize();
// Restart the ATG
}
// FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
} catch (error) {
- getLogger().error(this.logPrefix() + ' Charging station template file monitoring error: %j', error);
+ logger.error(this.logPrefix() + ' Charging station template file monitoring error: %j', error);
}
}
});
// Stop the ATG if needed
if (this.stationInfo.AutomaticTransactionGenerator.enable &&
this.stationInfo.AutomaticTransactionGenerator.stopOnConnectionFailure &&
- this.automaticTransactionGenerator &&
- this.automaticTransactionGenerator.started) {
+ 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);
const reconnectTimeout = (reconnectDelay - 100) > 0 && reconnectDelay;
- getLogger().error(`${this.logPrefix()} WebSocket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectTimeout}ms`);
+ logger.error(`${this.logPrefix()} WebSocket: connection retry in ${Utils.roundTo(reconnectDelay, 2)}ms, timeout ${reconnectTimeout}ms`);
await Utils.sleep(reconnectDelay);
- getLogger().error(this.logPrefix() + ' WebSocket: reconnecting try #' + this.autoReconnectRetryCount.toString());
+ logger.error(this.logPrefix() + ' WebSocket: reconnecting try #' + this.autoReconnectRetryCount.toString());
this.openWSConnection({ ...this.stationInfo.wsOptions, handshakeTimeout: reconnectTimeout }, true);
this.wsConnectionRestarted = true;
} else if (this.getAutoReconnectMaxRetries() !== -1) {
- getLogger().error(`${this.logPrefix()} WebSocket reconnect failure: max retries reached (${this.autoReconnectRetryCount}) or retry disabled (${this.getAutoReconnectMaxRetries()})`);
+ logger.error(`${this.logPrefix()} WebSocket reconnect failure: max retries reached (${this.autoReconnectRetryCount}) or retry disabled (${this.getAutoReconnectMaxRetries()})`);
}
}