Fix ATG execution time accuracy.
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 16 Sep 2021 21:23:36 +0000 (23:23 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 16 Sep 2021 21:23:36 +0000 (23:23 +0200)
And parallel run on each CS connectors

Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/utils/Constants.ts

index 04f05da59145fc36df53ff5e8d2313b0433c9ab3..963042a46684b0754de10962fd8c9e102f9ac819 100644 (file)
@@ -10,6 +10,8 @@ import logger from '../utils/Logger';
 
 export default class AutomaticTransactionGenerator {
   public timeToStop: boolean;
+  private startDate!: Date;
+  private stopDate!: Date;
   private chargingStation: ChargingStation;
 
   constructor(chargingStation: ChargingStation) {
@@ -17,29 +19,27 @@ export default class AutomaticTransactionGenerator {
     this.timeToStop = true;
   }
 
-  public async start(): Promise<void> {
+  public start(): void {
+    this.startDate = new Date();
+    this.stopDate = new Date(this.startDate.getTime() + (this.chargingStation.stationInfo?.AutomaticTransactionGenerator?.stopAfterHours ?? Constants.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS) * 3600 * 1000);
     this.timeToStop = false;
-    if (this.chargingStation.stationInfo.AutomaticTransactionGenerator.stopAfterHours &&
-      this.chargingStation.stationInfo.AutomaticTransactionGenerator.stopAfterHours > 0) {
-      // eslint-disable-next-line @typescript-eslint/no-misused-promises
-      setTimeout(async (): Promise<void> => {
-        await this.stop();
-      }, this.chargingStation.stationInfo.AutomaticTransactionGenerator.stopAfterHours * 3600 * 1000);
-    }
     for (const connector in this.chargingStation.connectors) {
       if (Utils.convertToInt(connector) > 0) {
-        await this.startConnector(Utils.convertToInt(connector));
+        // Avoid hogging the event loop with a busy loop
+        setImmediate(() => {
+          this.startOnConnector(Utils.convertToInt(connector)).catch(() => { /* This is intentional */ });
+        });
       }
     }
-    logger.info(this.logPrefix() + ' ATG started and will stop in ' + Utils.secondsToHHMMSS(this.chargingStation.stationInfo.AutomaticTransactionGenerator.stopAfterHours * 3600));
+    logger.info(this.logPrefix() + ' started and will run for ' + Utils.milliSecondsToHHMMSS(this.stopDate.getTime() - this.startDate.getTime()));
   }
 
   public async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise<void> {
-    logger.info(this.logPrefix() + ' ATG OVER => STOPPING ALL TRANSACTIONS');
+    logger.info(this.logPrefix() + ' over. Stopping all transactions');
     for (const connector in this.chargingStation.connectors) {
       const transactionId = this.chargingStation.getConnector(Utils.convertToInt(connector)).transactionId;
       if (this.chargingStation.getConnector(Utils.convertToInt(connector)).transactionStarted) {
-        logger.info(this.logPrefix(Utils.convertToInt(connector)) + ' ATG OVER. Stop transaction ' + transactionId.toString());
+        logger.info(this.logPrefix(Utils.convertToInt(connector)) + ' over. Stop transaction ' + transactionId.toString());
         await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
           this.chargingStation.getTransactionIdTag(transactionId), reason);
       }
@@ -47,10 +47,13 @@ export default class AutomaticTransactionGenerator {
     this.timeToStop = true;
   }
 
-  private async startConnector(connectorId: number): Promise<void> {
-    do {
-      if (this.timeToStop) {
-        logger.error(this.logPrefix(connectorId) + ' Entered in transaction loop while a request to stop it was made');
+  private async startOnConnector(connectorId: number): Promise<void> {
+    logger.info(this.logPrefix(connectorId) + ' started on connector');
+    let transactionSkip = 0;
+    let totalTransactionSkip = 0;
+    while (!this.timeToStop) {
+      if ((new Date()) > this.stopDate) {
+        await this.stop();
         break;
       }
       if (!this.chargingStation.isRegistered()) {
@@ -74,11 +77,11 @@ export default class AutomaticTransactionGenerator {
       }
       const wait = Utils.getRandomInt(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDelayBetweenTwoTransactions,
         this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDelayBetweenTwoTransactions) * 1000;
-      logger.info(this.logPrefix(connectorId) + ' wait for ' + Utils.milliSecondsToHHMMSS(wait));
+      logger.info(this.logPrefix(connectorId) + ' waiting for ' + Utils.milliSecondsToHHMMSS(wait));
       await Utils.sleep(wait);
       const start = Math.random();
-      let skip = 0;
       if (start < this.chargingStation.stationInfo.AutomaticTransactionGenerator.probabilityOfStart) {
+        transactionSkip = 0;
         // Start transaction
         const startResponse = await this.startTransaction(connectorId);
         if (startResponse?.idTagInfo?.status !== AuthorizationStatus.ACCEPTED) {
@@ -97,14 +100,14 @@ export default class AutomaticTransactionGenerator {
           }
         }
       } else {
-        skip++;
-        logger.info(this.logPrefix(connectorId) + ' transaction skipped ' + skip.toString());
+        transactionSkip++;
+        totalTransactionSkip++;
+        logger.info(this.logPrefix(connectorId) + ' skipped transaction ' + transactionSkip.toString() + '/' + totalTransactionSkip.toString());
       }
-    } while (!this.timeToStop);
-    logger.info(this.logPrefix(connectorId) + ' ATG STOPPED on the connector');
+    }
+    logger.info(this.logPrefix(connectorId) + ' stopped on connector');
   }
 
-  // eslint-disable-next-line consistent-this
   private async startTransaction(connectorId: number): Promise<StartTransactionResponse | AuthorizeResponse> {
     const measureId = 'StartTransaction with ATG';
     const beginId = PerformanceStatistics.beginMeasure(measureId);
@@ -136,7 +139,6 @@ export default class AutomaticTransactionGenerator {
     return startResponse;
   }
 
-  // eslint-disable-next-line consistent-this
   private async stopTransaction(connectorId: number): Promise<StopTransactionResponse> {
     const measureId = 'StopTransaction with ATG';
     const beginId = PerformanceStatistics.beginMeasure(measureId);
index 67bf3b627042e9c63bfc1e83a7df5ba07369558b..185eab5e504735608d2b3f184948fed422fcada1 100644 (file)
@@ -225,17 +225,17 @@ export default class ChargingStation {
   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];
@@ -248,11 +248,11 @@ export default class ChargingStation {
       }
     }
     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 {
@@ -866,8 +866,7 @@ export default class ChargingStation {
         this.automaticTransactionGeneration = new AutomaticTransactionGenerator(this);
       }
       if (this.automaticTransactionGeneration.timeToStop) {
-        // The ATG might sleep
-        this.automaticTransactionGeneration.start().catch(() => { });
+        this.automaticTransactionGeneration.start();
       }
     }
   }
index 006088a28d8ec080ace72e2d9892f6e4e2829bb7..1a0190e59d813d4c6b22bb9434aa198105c075e6 100644 (file)
@@ -120,7 +120,6 @@ export default class OCPP16RequestService extends OCPPRequestService {
     }
   }
 
-  // eslint-disable-next-line consistent-this
   public async sendMeterValues(connectorId: number, transactionId: number, interval: number, debug = false): Promise<void> {
     try {
       const meterValue: OCPP16MeterValue = {
index 593aa42a2405341ee4ecdc3d21f4f1ba9e7b8319..e6c28221d711b6620989d5735354a8d8388cbfa5 100644 (file)
@@ -32,6 +32,7 @@ export default class Constants {
   static readonly CHARGING_STATION_DEFAULT_RESET_TIME = 60000; // Ms
   static readonly CHARGING_STATION_ATG_WAIT_TIME = 2000; // Ms
   static readonly CHARGING_STATION_ATG_INITIALIZATION_TIME = 1000; // Ms
+  static readonly CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS = 0.25; // Hours
 
   static readonly TRANSACTION_DEFAULT_IDTAG = '00000000';