Uniformize log messages, take 2
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index d7dcdceec9428543c5957738912b541a61d47ddd..ff3b2601b8a7ba6ce9493272d06eb857a0db5109 100644 (file)
@@ -11,20 +11,19 @@ import WebSocket, { Data, RawData } from 'ws';
 import BaseError from '../exception/BaseError';
 import OCPPError from '../exception/OCPPError';
 import PerformanceStatistics from '../performance/PerformanceStatistics';
-import { AutomaticTransactionGeneratorConfiguration } from '../types/AutomaticTransactionGenerator';
-import ChargingStationConfiguration from '../types/ChargingStationConfiguration';
-import ChargingStationInfo from '../types/ChargingStationInfo';
-import ChargingStationOcppConfiguration from '../types/ChargingStationOcppConfiguration';
+import type { AutomaticTransactionGeneratorConfiguration } from '../types/AutomaticTransactionGenerator';
+import type ChargingStationConfiguration from '../types/ChargingStationConfiguration';
+import type ChargingStationInfo from '../types/ChargingStationInfo';
+import type ChargingStationOcppConfiguration from '../types/ChargingStationOcppConfiguration';
 import ChargingStationTemplate, {
   CurrentType,
   PowerUnits,
   WsOptions,
 } from '../types/ChargingStationTemplate';
-import { ChargingStationWorkerMessageEvents } from '../types/ChargingStationWorker';
 import { SupervisionUrlDistribution } from '../types/ConfigurationData';
-import { ConnectorStatus } from '../types/ConnectorStatus';
+import type { ConnectorStatus } from '../types/ConnectorStatus';
 import { FileType } from '../types/FileType';
-import { JsonType } from '../types/JsonType';
+import type { JsonType } from '../types/JsonType';
 import { ChargePointErrorCode } from '../types/ocpp/ChargePointErrorCode';
 import { ChargePointStatus } from '../types/ocpp/ChargePointStatus';
 import { ChargingProfile, ChargingRateUnitType } from '../types/ocpp/ChargingProfile';
@@ -74,12 +73,14 @@ import AuthorizedTagsCache from './AuthorizedTagsCache';
 import AutomaticTransactionGenerator from './AutomaticTransactionGenerator';
 import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
 import { ChargingStationUtils } from './ChargingStationUtils';
+import ChargingStationWorkerBroadcastChannel from './ChargingStationWorkerBroadcastChannel';
+import { MessageChannelUtils } from './MessageChannelUtils';
 import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService';
 import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
 import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService';
 import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
-import OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
-import OCPPRequestService from './ocpp/OCPPRequestService';
+import type OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
+import type OCPPRequestService from './ocpp/OCPPRequestService';
 import SharedLRUCache from './SharedLRUCache';
 
 export default class ChargingStation {
@@ -87,6 +88,7 @@ export default class ChargingStation {
   public readonly templateFile: string;
   public authorizedTagsCache: AuthorizedTagsCache;
   public stationInfo!: ChargingStationInfo;
+  public stopped: boolean;
   public readonly connectors: Map<number, ConnectorStatus>;
   public ocppConfiguration!: ChargingStationOcppConfiguration;
   public wsConnection!: WebSocket;
@@ -106,11 +108,11 @@ export default class ChargingStation {
   private configuredSupervisionUrl!: URL;
   private wsConnectionRestarted: boolean;
   private autoReconnectRetryCount: number;
-  private stopped: boolean;
   private templateFileWatcher!: fs.FSWatcher;
   private readonly sharedLRUCache: SharedLRUCache;
   private automaticTransactionGenerator!: AutomaticTransactionGenerator;
   private webSocketPingSetInterval!: NodeJS.Timeout;
+  private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
 
   constructor(index: number, templateFile: string) {
     this.index = index;
@@ -123,6 +125,8 @@ export default class ChargingStation {
     this.connectors = new Map<number, ConnectorStatus>();
     this.requests = new Map<string, CachedRequest>();
     this.messageBuffer = new Set<string>();
+    this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
+
     this.initialize();
   }
 
@@ -178,6 +182,10 @@ export default class ChargingStation {
     return this.stationInfo.mayAuthorizeAtRemoteStart ?? true;
   }
 
+  public getPayloadSchemaValidation(): boolean | undefined {
+    return this.stationInfo.payloadSchemaValidation ?? true;
+  }
+
   public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined {
     const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
     switch (this.getCurrentOutType(stationInfo)) {
@@ -328,22 +336,22 @@ export default class ChargingStation {
     }
   }
 
-  public getEnergyActiveImportRegisterByTransactionId(transactionId: number): number | undefined {
+  public getEnergyActiveImportRegisterByTransactionId(transactionId: number): number {
     const transactionConnectorStatus = this.getConnectorStatus(
       this.getConnectorIdByTransactionId(transactionId)
     );
     if (this.getMeteringPerTransaction()) {
-      return transactionConnectorStatus?.transactionEnergyActiveImportRegisterValue;
+      return transactionConnectorStatus?.transactionEnergyActiveImportRegisterValue ?? 0;
     }
-    return transactionConnectorStatus?.energyActiveImportRegisterValue;
+    return transactionConnectorStatus?.energyActiveImportRegisterValue ?? 0;
   }
 
-  public getEnergyActiveImportRegisterByConnectorId(connectorId: number): number | undefined {
+  public getEnergyActiveImportRegisterByConnectorId(connectorId: number): number {
     const connectorStatus = this.getConnectorStatus(connectorId);
     if (this.getMeteringPerTransaction()) {
-      return connectorStatus?.transactionEnergyActiveImportRegisterValue;
+      return connectorStatus?.transactionEnergyActiveImportRegisterValue ?? 0;
     }
-    return connectorStatus?.energyActiveImportRegisterValue;
+    return connectorStatus?.energyActiveImportRegisterValue ?? 0;
   }
 
   public getAuthorizeRemoteTxRequests(): boolean {
@@ -508,17 +516,14 @@ export default class ChargingStation {
             // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
           } catch (error) {
             logger.error(
-              `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error: %j`,
+              `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
               error
             );
           }
         }
       }
     );
-    parentPort.postMessage({
-      id: ChargingStationWorkerMessageEvents.STARTED,
-      data: { id: this.stationInfo.chargingStationId },
-    });
+    parentPort.postMessage(MessageChannelUtils.buildStartedMessage(this));
   }
 
   public async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise<void> {
@@ -545,11 +550,8 @@ export default class ChargingStation {
     this.templateFileWatcher.close();
     this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
     this.bootNotificationResponse = null;
-    parentPort.postMessage({
-      id: ChargingStationWorkerMessageEvents.STOPPED,
-      data: { id: this.stationInfo.chargingStationId },
-    });
     this.stopped = true;
+    parentPort.postMessage(MessageChannelUtils.buildStoppedMessage(this));
   }
 
   public async reset(reason?: StopTransactionReason): Promise<void> {
@@ -581,7 +583,7 @@ export default class ChargingStation {
     if (!Utils.isEmptyArray(chargingProfiles)) {
       const result = ChargingStationUtils.getLimitFromChargingProfiles(
         chargingProfiles,
-        Utils.logPrefix()
+        this.logPrefix()
       );
       if (!Utils.isNullOrUndefined(result)) {
         limit = result.limit;
@@ -624,7 +626,7 @@ export default class ChargingStation {
       );
       this.getConnectorStatus(connectorId).chargingProfiles = [];
     }
-    if (!Array.isArray(this.getConnectorStatus(connectorId).chargingProfiles)) {
+    if (Array.isArray(this.getConnectorStatus(connectorId).chargingProfiles) === false) {
       logger.error(
         `${this.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
       );
@@ -673,6 +675,74 @@ export default class ChargingStation {
     this.messageBuffer.add(message);
   }
 
+  public openWSConnection(
+    options: WsOptions = this.stationInfo?.wsOptions ?? {},
+    params: { closeOpened?: boolean; terminateOpened?: boolean } = {
+      closeOpened: false,
+      terminateOpened: false,
+    }
+  ): void {
+    options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
+    params.closeOpened = params?.closeOpened ?? false;
+    params.terminateOpened = params?.terminateOpened ?? false;
+    if (
+      !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
+      !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
+    ) {
+      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
+    }
+    if (params?.closeOpened) {
+      this.closeWSConnection();
+    }
+    if (params?.terminateOpened) {
+      this.terminateWSConnection();
+    }
+    let protocol: string;
+    switch (this.getOcppVersion()) {
+      case OCPPVersion.VERSION_16:
+        protocol = 'ocpp' + OCPPVersion.VERSION_16;
+        break;
+      default:
+        this.handleUnsupportedVersion(this.getOcppVersion());
+        break;
+    }
+
+    logger.info(
+      this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString()
+    );
+
+    this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
+
+    // Handle WebSocket message
+    this.wsConnection.on(
+      'message',
+      this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void
+    );
+    // Handle WebSocket error
+    this.wsConnection.on(
+      'error',
+      this.onError.bind(this) as (this: WebSocket, error: Error) => void
+    );
+    // Handle WebSocket close
+    this.wsConnection.on(
+      'close',
+      this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void
+    );
+    // Handle WebSocket open
+    this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void);
+    // Handle WebSocket ping
+    this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void);
+    // Handle WebSocket pong
+    this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void);
+  }
+
+  public closeWSConnection(): void {
+    if (this.isWebSocketConnectionOpened()) {
+      this.wsConnection.close();
+      this.wsConnection = null;
+    }
+  }
+
   private flushMessageBuffer() {
     if (this.messageBuffer.size > 0) {
       this.messageBuffer.forEach((message) => {
@@ -776,14 +846,14 @@ export default class ChargingStation {
     ChargingStationUtils.checkConfiguredMaxConnectors(
       configuredMaxConnectors,
       this.templateFile,
-      Utils.logPrefix()
+      this.logPrefix()
     );
     const templateMaxConnectors =
       ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate);
     ChargingStationUtils.checkTemplateMaxConnectors(
       templateMaxConnectors,
       this.templateFile,
-      Utils.logPrefix()
+      this.logPrefix()
     );
     if (
       configuredMaxConnectors >
@@ -850,11 +920,9 @@ export default class ChargingStation {
   }
 
   private handleUnsupportedVersion(version: OCPPVersion) {
-    const errMsg = `${this.logPrefix()} Unsupported protocol version '${version}' configured in template file ${
-      this.templateFile
-    }`;
-    logger.error(errMsg);
-    throw new Error(errMsg);
+    const errMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
+    logger.error(`${this.logPrefix()} ${errMsg}`);
+    throw new BaseError(errMsg);
   }
 
   private initialize(): void {
@@ -1072,10 +1140,8 @@ export default class ChargingStation {
     templateMaxConnectors: number
   ): void {
     if (!stationInfo?.Connectors && this.connectors.size === 0) {
-      const logMsg = `${this.logPrefix()} No already defined connectors and charging station information from template ${
-        this.templateFile
-      } with no connectors configuration defined`;
-      logger.error(logMsg);
+      const logMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
+      logger.error(`${this.logPrefix()} ${logMsg}`);
       throw new BaseError(logMsg);
     }
     if (!stationInfo?.Connectors[0]) {
@@ -1349,7 +1415,7 @@ export default class ChargingStation {
     let errMsg: string;
     try {
       const request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
-      if (Utils.isIterable(request)) {
+      if (Array.isArray(request) === true) {
         [messageType, messageId] = request;
         // Check the type of message
         switch (messageType) {
@@ -1386,12 +1452,12 @@ export default class ChargingStation {
             }
             // Respond
             cachedRequest = this.requests.get(messageId);
-            if (Utils.isIterable(cachedRequest)) {
+            if (Array.isArray(cachedRequest) === true) {
               [responseCallback, , requestCommandName, requestPayload] = cachedRequest;
             } else {
               throw new OCPPError(
                 ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} response is not iterable`,
+                `Cached request for message id ${messageId} response is not an array`,
                 null,
                 cachedRequest as unknown as JsonType
               );
@@ -1416,12 +1482,12 @@ export default class ChargingStation {
               );
             }
             cachedRequest = this.requests.get(messageId);
-            if (Utils.isIterable(cachedRequest)) {
+            if (Array.isArray(cachedRequest) === true) {
               [, errorCallback, requestCommandName] = cachedRequest;
             } else {
               throw new OCPPError(
                 ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} error response is not iterable`,
+                `Cached request for message id ${messageId} error response is not an array`,
                 null,
                 cachedRequest as unknown as JsonType
               );
@@ -1436,31 +1502,31 @@ export default class ChargingStation {
           // Error
           default:
             // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            errMsg = `${this.logPrefix()} Wrong message type ${messageType}`;
-            logger.error(errMsg);
+            errMsg = `Wrong message type ${messageType}`;
+            logger.error(`${this.logPrefix()} ${errMsg}`);
             throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
         }
+        parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
       } else {
-        throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not iterable', null, {
+        throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming message is not an array', null, {
           payload: request,
         });
       }
     } catch (error) {
       // Log
       logger.error(
-        "%s Incoming OCPP '%s' message '%j' matching cached request '%j' processing error: %j",
-        this.logPrefix(),
-        commandName ?? requestCommandName ?? null,
-        data.toString(),
-        this.requests.get(messageId),
+        `${this.logPrefix()} Incoming OCPP command '${
+          commandName ?? requestCommandName ?? null
+        }' message '${data.toString()}' matching cached request '${JSON.stringify(
+          this.requests.get(messageId)
+        )}' processing error:`,
         error
       );
       if (!(error instanceof OCPPError)) {
         logger.warn(
-          "%s Error thrown at incoming OCPP '%s' message '%j' handling is not an OCPPError: %j",
-          this.logPrefix(),
-          commandName ?? requestCommandName ?? null,
-          data.toString(),
+          `${this.logPrefix()} Error thrown at incoming OCPP command '${
+            commandName ?? requestCommandName ?? null
+          }' message '${data.toString()}' handling is not an OCPPError:`,
           error
         );
       }
@@ -1485,7 +1551,7 @@ export default class ChargingStation {
 
   private onError(error: WSError): void {
     this.closeWSConnection();
-    logger.error(this.logPrefix() + ' WebSocket error: %j', error);
+    logger.error(this.logPrefix() + ' WebSocket error:', error);
   }
 
   private getUseConnectorId0(stationInfo?: ChargingStationInfo): boolean | undefined {
@@ -1865,74 +1931,6 @@ export default class ChargingStation {
     }
   }
 
-  private openWSConnection(
-    options: WsOptions = this.stationInfo?.wsOptions ?? {},
-    params: { closeOpened?: boolean; terminateOpened?: boolean } = {
-      closeOpened: false,
-      terminateOpened: false,
-    }
-  ): void {
-    options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
-    params.closeOpened = params?.closeOpened ?? false;
-    params.terminateOpened = params?.terminateOpened ?? false;
-    if (
-      !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
-      !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
-    ) {
-      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
-    }
-    if (params?.closeOpened) {
-      this.closeWSConnection();
-    }
-    if (params?.terminateOpened) {
-      this.terminateWSConnection();
-    }
-    let protocol: string;
-    switch (this.getOcppVersion()) {
-      case OCPPVersion.VERSION_16:
-        protocol = 'ocpp' + OCPPVersion.VERSION_16;
-        break;
-      default:
-        this.handleUnsupportedVersion(this.getOcppVersion());
-        break;
-    }
-
-    logger.info(
-      this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString()
-    );
-
-    this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
-
-    // Handle WebSocket message
-    this.wsConnection.on(
-      'message',
-      this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void
-    );
-    // Handle WebSocket error
-    this.wsConnection.on(
-      'error',
-      this.onError.bind(this) as (this: WebSocket, error: Error) => void
-    );
-    // Handle WebSocket close
-    this.wsConnection.on(
-      'close',
-      this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void
-    );
-    // Handle WebSocket open
-    this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void);
-    // Handle WebSocket ping
-    this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void);
-    // Handle WebSocket pong
-    this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void);
-  }
-
-  private closeWSConnection(): void {
-    if (this.isWebSocketConnectionOpened()) {
-      this.wsConnection.close();
-      this.wsConnection = null;
-    }
-  }
-
   private terminateWSConnection(): void {
     if (this.isWebSocketConnectionOpened()) {
       this.wsConnection.terminate();