MeterValues: add Transaction.Begin and .End support
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 12 Jun 2021 15:39:49 +0000 (17:39 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 12 Jun 2021 15:39:49 +0000 (17:39 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
README.md
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts [new file with mode: 0644]
src/charging-station/ocpp/OCPPRequestService.ts
src/types/ChargingStationTemplate.ts
src/types/Connectors.ts

index a525ad66f15ecb049f17d8b1daa30c9fceb016f7..148665b43b17557293178b8fb7a70eec4a34386b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ power | | | integer\|integer[] | charging stations maximum power value(s)
 powerSharedByConnectors | true/false | false | boolean | charging stations power shared by its connectors
 powerUnit | W/kW | W | string | charging stations power unit
 currentOutType | AC/DC | AC | string | charging stations current out type
+voltageOut | | AC:230/DC:400 | integer | charging stations voltage out
 numberOfPhases | 0/1/3 | AC:3/DC:0 | integer | charging stations number of phase(s) 
 numberOfConnectors | | | integer\|integer[] | charging stations number of connector(s)
 useConnectorId0 | true/false | true | boolean | use connector id 0 definition from the template
@@ -66,8 +67,10 @@ connectionTimeout | | 30 | integer | connection timeout to the OCPP-J server
 autoReconnectMaxRetries | | -1 (unlimited) | integer | connection retries to the OCPP-J server
 reconnectExponentialDelay | true/false | false | boolean | connection delay retry to the OCPP-J server
 registrationMaxRetries | | -1 (unlimited) | integer | charging stations boot notification retries
-enableStatistics | true/false | true | boolean | enable charging stations statistics   
-voltageOut | | AC:230/DC:400 | integer | charging stations voltage out
+enableStatistics | true/false | true | boolean | enable charging stations statistics
+beginEndMeterValues | true/false | false | boolean | enable Transaction.{Begin,End} MeterValues
+outOfOrderEndMeterValues | true/false | false | boolean | send Transaction.End MeterValues out of order
+meteringPerTransaction | true/false | true | boolean | disable metering on a per transaction basis
 Configuration | | | ChargingStationConfiguration | charging stations OCPP configuration parameters
 AutomaticTransactionGenerator | | | AutomaticTransactionGenerator | charging stations ATG configuration
 Connectors | | | Connectors | charging stations connectors configuration
index 7268f4459c9d7ef5ba6818534a5725445ca8b3af..942410eba9c6115a5a8a6284c75b42eb4c9d5723 100644 (file)
@@ -46,7 +46,7 @@ export default class AutomaticTransactionGenerator {
       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());
-        await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getTransactionMeterStop(transactionId),
+        await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
           this.chargingStation.getTransactionIdTag(transactionId), reason);
       }
     }
@@ -149,7 +149,7 @@ export default class AutomaticTransactionGenerator {
   // eslint-disable-next-line consistent-this
   private async stopTransaction(connectorId: number, self: AutomaticTransactionGenerator): Promise<StopTransactionResponse> {
     const transactionId = self.chargingStation.getConnector(connectorId).transactionId;
-    return await self.chargingStation.ocppRequestService.sendStopTransaction(transactionId, self.chargingStation.getTransactionMeterStop(transactionId),
+    return await self.chargingStation.ocppRequestService.sendStopTransaction(transactionId, self.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
       self.chargingStation.getTransactionIdTag(transactionId));
   }
 
index e9eb86b453189382b0b09cf255123de5ebddbf6d..503231279b9e00f781f418113629122d06bc9fc3 100644 (file)
@@ -149,14 +149,40 @@ export default class ChargingStation {
     }
   }
 
-  public getTransactionMeterStop(transactionId: number): number | undefined {
+  public getOutOfOrderEndMeterValues(): boolean {
+    return this.stationInfo.outOfOrderEndMeterValues ?? false;
+  }
+
+  public getBeginEndMeterValues(): boolean {
+    return this.stationInfo.beginEndMeterValues ?? false;
+  }
+
+  public getMeteringPerTransaction(): boolean {
+    return this.stationInfo.meteringPerTransaction ?? true;
+  }
+
+  public getEnergyActiveImportRegisterByTransactionId(transactionId: number): number | undefined {
+    if (this.getMeteringPerTransaction()) {
+      for (const connector in this.connectors) {
+        if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
+          return this.getConnector(Utils.convertToInt(connector)).transactionEnergyActiveImportRegisterValue;
+        }
+      }
+    }
     for (const connector in this.connectors) {
       if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
-        return this.getConnector(Utils.convertToInt(connector)).lastEnergyActiveImportRegisterValue;
+        return this.getConnector(Utils.convertToInt(connector)).energyActiveImportRegisterValue;
       }
     }
   }
 
+  public getEnergyActiveImportRegisterByConnectorId(connectorId: number): number | undefined {
+    if (this.getMeteringPerTransaction()) {
+      return this.getConnector(connectorId).transactionEnergyActiveImportRegisterValue;
+    }
+    return this.getConnector(connectorId).energyActiveImportRegisterValue;
+  }
+
   public getAuthorizeRemoteTxRequests(): boolean {
     const authorizeRemoteTxRequests = this.getConfigurationKey(StandardParametersKey.AuthorizeRemoteTxRequests);
     return authorizeRemoteTxRequests ? Utils.convertToBoolean(authorizeRemoteTxRequests.value) : false;
@@ -319,7 +345,10 @@ export default class ChargingStation {
   }
 
   public resetTransactionOnConnector(connectorId: number): void {
-    this.initTransactionOnConnector(connectorId);
+    this.getConnector(connectorId).transactionStarted = false;
+    delete this.getConnector(connectorId).transactionId;
+    delete this.getConnector(connectorId).idTag;
+    this.getConnector(connectorId).transactionEnergyActiveImportRegisterValue = 0;
     this.stopMeterValues(connectorId);
   }
 
@@ -448,7 +477,7 @@ export default class ChargingStation {
     // Initialize transaction attributes on connectors
     for (const connector in this.connectors) {
       if (Utils.convertToInt(connector) > 0 && !this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
-        this.initTransactionOnConnector(Utils.convertToInt(connector));
+        this.initTransactionAttributesOnConnector(Utils.convertToInt(connector));
       }
     }
     switch (this.getOCPPVersion()) {
@@ -756,7 +785,8 @@ export default class ChargingStation {
       for (const connector in this.connectors) {
         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.getTransactionMeterStop(transactionId), this.getTransactionIdTag(transactionId), reason);
+          await this.ocppRequestService.sendStopTransaction(transactionId, this.getEnergyActiveImportRegisterByTransactionId(transactionId),
+            this.getTransactionIdTag(transactionId), reason);
         }
       }
     }
@@ -916,11 +946,10 @@ export default class ChargingStation {
     }
   }
 
-  private initTransactionOnConnector(connectorId: number): void {
+  private initTransactionAttributesOnConnector(connectorId: number): void {
     this.getConnector(connectorId).transactionStarted = false;
-    delete this.getConnector(connectorId).transactionId;
-    delete this.getConnector(connectorId).idTag;
-    this.getConnector(connectorId).lastEnergyActiveImportRegisterValue = -1;
+    this.getConnector(connectorId).energyActiveImportRegisterValue = 0;
+    this.getConnector(connectorId).transactionEnergyActiveImportRegisterValue = 0;
   }
 }
 
index f230b3bcbad1b30ef21aeec40e866ee20ea33899..ba1f2f7ef274bc034d7a8311b47e11ecd86cd170 100644 (file)
@@ -63,8 +63,10 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     }
     if (this.chargingStation.getConnector(connectorId)?.transactionStarted) {
       const transactionId = this.chargingStation.getConnector(connectorId).transactionId;
-      const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getTransactionMeterStop(transactionId),
-        this.chargingStation.getTransactionIdTag(transactionId), OCPP16StopTransactionReason.UNLOCK_COMMAND);
+      const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId,
+        this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
+        this.chargingStation.getTransactionIdTag(transactionId),
+        OCPP16StopTransactionReason.UNLOCK_COMMAND);
       if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
         return Constants.OCPP_RESPONSE_UNLOCKED;
       }
@@ -297,7 +299,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
       if (Utils.convertToInt(connector) > 0 && this.chargingStation.getConnector(Utils.convertToInt(connector))?.transactionId === transactionId) {
         await this.chargingStation.ocppRequestService.sendStatusNotification(Utils.convertToInt(connector), OCPP16ChargePointStatus.FINISHING);
         this.chargingStation.getConnector(Utils.convertToInt(connector)).status = OCPP16ChargePointStatus.FINISHING;
-        await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getTransactionMeterStop(transactionId),
+        await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
           this.chargingStation.getTransactionIdTag(transactionId));
         return Constants.OCPP_RESPONSE_ACCEPTED;
       }
index a5721325e025c51b5819c294f434d2ccd71c8c32..4f2cac67ebb4442977aea2cf443ae8cecbae2068 100644 (file)
@@ -1,6 +1,6 @@
 import { AuthorizeRequest, OCPP16AuthorizeResponse, OCPP16StartTransactionResponse, OCPP16StopTransactionReason, OCPP16StopTransactionResponse, StartTransactionRequest, StopTransactionRequest } from '../../../types/ocpp/1.6/Transaction';
 import { HeartbeatRequest, OCPP16BootNotificationRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, StatusNotificationRequest } from '../../../types/ocpp/1.6/Requests';
-import { MeterValue, MeterValueContext, MeterValueLocation, MeterValuePhase, MeterValueUnit, MeterValuesRequest, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
+import { MeterValue, MeterValueContext, MeterValuePhase, MeterValuesRequest, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
 
 import { ACElectricUtils } from '../../../utils/ElectricUtils';
 import Constants from '../../../utils/Constants';
@@ -10,6 +10,7 @@ import { MessageType } from '../../../types/ocpp/MessageType';
 import { OCPP16BootNotificationResponse } from '../../../types/ocpp/1.6/Responses';
 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
+import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
 import OCPPError from '../../OcppError';
 import OCPPRequestService from '../OCPPRequestService';
@@ -75,12 +76,10 @@ export default class OCPP16RequestService extends OCPPRequestService {
       const payload: StartTransactionRequest = {
         connectorId,
         ...!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.TRANSACTION_DEFAULT_IDTAG },
-        meterStart: 0,
+        meterStart: this.chargingStation.getEnergyActiveImportRegisterByConnectorId(connectorId),
         timestamp: new Date().toISOString(),
       };
-      const response = await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.START_TRANSACTION) as OCPP16StartTransactionResponse;
-      await this.sendTransactionBeginMeterValues(connectorId, response.transactionId, 0);
-      return response;
+      return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.START_TRANSACTION) as OCPP16StartTransactionResponse;
     } catch (error) {
       this.handleRequestError(OCPP16RequestCommand.START_TRANSACTION, error);
     }
@@ -102,7 +101,8 @@ export default class OCPP16RequestService extends OCPPRequestService {
           connectorId = Utils.convertToInt(connector);
         }
       }
-      await this.sendTransactionEndMeterValues(connectorId, transactionId, meterStop);
+      (this.chargingStation.getBeginEndMeterValues() && !this.chargingStation.getOutOfOrderEndMeterValues())
+        && await this.sendTransactionEndMeterValues(connectorId, transactionId, meterStop);
       return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.STOP_TRANSACTION) as OCPP16StartTransactionResponse;
     } catch (error) {
       this.handleRequestError(OCPP16RequestCommand.STOP_TRANSACTION, error);
@@ -121,27 +121,15 @@ export default class OCPP16RequestService extends OCPPRequestService {
         const connector = self.chargingStation.getConnector(connectorId);
         // SoC measurand
         if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.STATE_OF_CHARGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.STATE_OF_CHARGE)) {
-          meterValue.sampledValue.push({
-            ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.PERCENT },
-            ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-            measurand: meterValuesTemplate[index].measurand,
-            ...!Utils.isUndefined(meterValuesTemplate[index].location) ? { location: meterValuesTemplate[index].location } : { location: MeterValueLocation.EV },
-            ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: Utils.getRandomInt(100).toString() },
-          });
+          meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.getRandomInt(100)));
           const sampledValuesIndex = meterValue.sampledValue.length - 1;
           if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
-            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`);
+            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`);
           }
         // Voltage measurand
         } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.VOLTAGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.VOLTAGE)) {
           const voltageMeasurandValue = Utils.getRandomFloatRounded(self.chargingStation.getVoltageOut() + self.chargingStation.getVoltageOut() * 0.1, self.chargingStation.getVoltageOut() - self.chargingStation.getVoltageOut() * 0.1);
-          meterValue.sampledValue.push({
-            ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.VOLT },
-            ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-            measurand: meterValuesTemplate[index].measurand,
-            ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-            ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue.toString() },
-          });
+          meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], voltageMeasurandValue));
           for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
             let phaseValue: string;
             if (self.chargingStation.getVoltageOut() >= 0 && self.chargingStation.getVoltageOut() <= 250) {
@@ -149,28 +137,13 @@ export default class OCPP16RequestService extends OCPPRequestService {
             } else if (self.chargingStation.getVoltageOut() > 250) {
               phaseValue = `L${phase}-L${(phase + 1) % self.chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % self.chargingStation.getNumberOfPhases() : self.chargingStation.getNumberOfPhases()}`;
             }
-            meterValue.sampledValue.push({
-              ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.VOLT },
-              ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-              measurand: meterValuesTemplate[index].measurand,
-              ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-              ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue.toString() },
-              phase: phaseValue as MeterValuePhase,
-            });
+            meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], voltageMeasurandValue, null,
+              phaseValue as MeterValuePhase));
           }
         // Power.Active.Import measurand
         } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT)) {
-          // FIXME: factor out powerDivider checks
-          if (Utils.isUndefined(self.chargingStation.stationInfo.powerDivider)) {
-            const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
-            logger.error(errMsg);
-            throw Error(errMsg);
-          } else if (self.chargingStation.stationInfo.powerDivider && self.chargingStation.stationInfo.powerDivider <= 0) {
-            const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${self.chargingStation.stationInfo.powerDivider}`;
-            logger.error(errMsg);
-            throw Error(errMsg);
-          }
-          const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
+          OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand);
+          const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
           const powerMeasurandValues = {} as MeasurandValues;
           const maxPower = Math.round(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider);
           const maxPowerPerPhase = Math.round((self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider) / self.chargingStation.getNumberOfPhases());
@@ -196,41 +169,20 @@ export default class OCPP16RequestService extends OCPPRequestService {
               logger.error(errMsg);
               throw Error(errMsg);
           }
-          meterValue.sampledValue.push({
-            ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT },
-            ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-            measurand: meterValuesTemplate[index].measurand,
-            ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-            ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: powerMeasurandValues.allPhases.toString() },
-          });
+          meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], powerMeasurandValues.allPhases));
           const sampledValuesIndex = meterValue.sampledValue.length - 1;
           if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPower || debug) {
-            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPower}`);
+            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPower}`);
           }
           for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
             const phaseValue = `L${phase}-N`;
-            meterValue.sampledValue.push({
-              ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT },
-              ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-              ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
-              ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-              ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: powerMeasurandValues[`L${phase}`] as string },
-              phase: phaseValue as MeterValuePhase,
-            });
+            meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], powerMeasurandValues[`L${phase}`], null,
+              phaseValue as MeterValuePhase));
           }
         // Current.Import measurand
         } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.CURRENT_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.CURRENT_IMPORT)) {
-          // FIXME: factor out powerDivider checks
-          if (Utils.isUndefined(self.chargingStation.stationInfo.powerDivider)) {
-            const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
-            logger.error(errMsg);
-            throw Error(errMsg);
-          } else if (self.chargingStation.stationInfo.powerDivider && self.chargingStation.stationInfo.powerDivider <= 0) {
-            const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${self.chargingStation.stationInfo.powerDivider}`;
-            logger.error(errMsg);
-            throw Error(errMsg);
-          }
-          const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
+          OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand);
+          const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
           const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
           let maxAmperage: number;
           switch (self.chargingStation.getCurrentOutType()) {
@@ -257,65 +209,41 @@ export default class OCPP16RequestService extends OCPPRequestService {
               logger.error(errMsg);
               throw Error(errMsg);
           }
-          meterValue.sampledValue.push({
-            ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.AMP },
-            ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-            measurand: meterValuesTemplate[index].measurand,
-            ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-            ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: currentMeasurandValues.allPhases.toString() },
-          });
+          meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], currentMeasurandValues.allPhases));
           const sampledValuesIndex = meterValue.sampledValue.length - 1;
           if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage || debug) {
-            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
+            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
           }
           for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
             const phaseValue = `L${phase}`;
-            meterValue.sampledValue.push({
-              ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.AMP },
-              ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-              ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
-              ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-              ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: currentMeasurandValues[phaseValue] as string },
-              phase: phaseValue as MeterValuePhase,
-            });
+            meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], currentMeasurandValues[phaseValue], null,
+              phaseValue as MeterValuePhase));
           }
         // Energy.Active.Import.Register measurand (default)
         } else if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-          // FIXME: factor out powerDivider checks
-          if (Utils.isUndefined(self.chargingStation.stationInfo.powerDivider)) {
-            const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
-            logger.error(errMsg);
-            throw Error(errMsg);
-          } else if (self.chargingStation.stationInfo.powerDivider && self.chargingStation.stationInfo.powerDivider <= 0) {
-            const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${self.chargingStation.stationInfo.powerDivider}`;
-            logger.error(errMsg);
-            throw Error(errMsg);
-          }
+          OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand);
           if (Utils.isUndefined(meterValuesTemplate[index].value)) {
             const measurandValue = Utils.getRandomInt(self.chargingStation.stationInfo.maxPower / (self.chargingStation.stationInfo.powerDivider * 3600000) * interval);
             // Persist previous value in connector
-            if (connector && !Utils.isNullOrUndefined(connector.lastEnergyActiveImportRegisterValue) && connector.lastEnergyActiveImportRegisterValue >= 0) {
-              connector.lastEnergyActiveImportRegisterValue += measurandValue;
+            if (connector && !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && connector.energyActiveImportRegisterValue >= 0 &&
+                !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && connector.transactionEnergyActiveImportRegisterValue >= 0) {
+              connector.energyActiveImportRegisterValue += measurandValue;
+              connector.transactionEnergyActiveImportRegisterValue += measurandValue;
             } else {
-              connector.lastEnergyActiveImportRegisterValue = 0;
+              connector.energyActiveImportRegisterValue = 0;
+              connector.transactionEnergyActiveImportRegisterValue = 0;
             }
           }
-          meterValue.sampledValue.push({
-            ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT_HOUR },
-            ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
-            ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
-            ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-            ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } :
-              { value: connector.lastEnergyActiveImportRegisterValue.toString() },
-          });
+          meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index],
+            self.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId)));
           const sampledValuesIndex = meterValue.sampledValue.length - 1;
           const maxConsumption = Math.round(self.chargingStation.stationInfo.maxPower * 3600 / (self.chargingStation.stationInfo.powerDivider * interval));
           if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxConsumption || debug) {
-            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxConsumption}`);
+            logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxConsumption}`);
           }
         // Unsupported measurand
         } else {
-          logger.info(`${self.chargingStation.logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} on connectorId ${connectorId}`);
+          logger.info(`${self.chargingStation.logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} on connectorId ${connectorId}`);
         }
       }
       const payload: MeterValuesRequest = {
@@ -329,12 +257,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
     }
   }
 
-  public async sendError(messageId: string, error: OCPPError, commandName: OCPP16RequestCommand | OCPP16IncomingRequestCommand): Promise<unknown> {
-    // Send error
-    return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName);
-  }
-
-  private async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number) {
+  public async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number): Promise<void> {
     const meterValue: MeterValue = {
       timestamp: new Date().toISOString(),
       sampledValue: [],
@@ -343,14 +266,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
     for (let index = 0; index < meterValuesTemplate.length; index++) {
       // Energy.Active.Import.Register measurand (default)
       if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-        meterValue.sampledValue.push({
-          ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT_HOUR },
-          context: MeterValueContext.TRANSACTION_BEGIN,
-          ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
-          ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-          ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } :
-            { value: meterBegin.toString() },
-        });
+        meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], meterBegin, MeterValueContext.TRANSACTION_BEGIN));
       }
     }
     const payload: MeterValuesRequest = {
@@ -361,7 +277,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
     await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
   }
 
-  private async sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number) {
+  public async sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number): Promise<void> {
     const meterValue: MeterValue = {
       timestamp: new Date().toISOString(),
       sampledValue: [],
@@ -370,14 +286,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
     for (let index = 0; index < meterValuesTemplate.length; index++) {
       // Energy.Active.Import.Register measurand (default)
       if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-        meterValue.sampledValue.push({
-          ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT_HOUR },
-          context: MeterValueContext.TRANSACTION_END,
-          ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
-          ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
-          ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } :
-            { value: meterEnd.toString() },
-        });
+        meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], meterEnd, MeterValueContext.TRANSACTION_END));
       }
     }
     const payload: MeterValuesRequest = {
@@ -387,4 +296,9 @@ export default class OCPP16RequestService extends OCPPRequestService {
     };
     await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
   }
+
+  public async sendError(messageId: string, error: OCPPError, commandName: OCPP16RequestCommand | OCPP16IncomingRequestCommand): Promise<unknown> {
+    // Send error
+    return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName);
+  }
 }
index e9d2d2e9dc18477b70769f9d79682ea8b51f4700..0f12f2f2c5f0def0c65aee07b80a422e4891229e 100644 (file)
@@ -54,7 +54,9 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       this.chargingStation.getConnector(connectorId).transactionStarted = true;
       this.chargingStation.getConnector(connectorId).transactionId = payload.transactionId;
       this.chargingStation.getConnector(connectorId).idTag = requestPayload.idTag;
-      this.chargingStation.getConnector(connectorId).lastEnergyActiveImportRegisterValue = 0;
+      this.chargingStation.getConnector(connectorId).transactionEnergyActiveImportRegisterValue = 0;
+      this.chargingStation.getBeginEndMeterValues() && await this.chargingStation.ocppRequestService.sendTransactionBeginMeterValues(connectorId, payload.transactionId,
+        this.chargingStation.getEnergyActiveImportRegisterByTransactionId(payload.transactionId));
       await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, OCPP16ChargePointStatus.CHARGING);
       this.chargingStation.getConnector(connectorId).status = OCPP16ChargePointStatus.CHARGING;
       logger.info(this.chargingStation.logPrefix() + ' Transaction ' + payload.transactionId.toString() + ' STARTED on ' + this.chargingStation.stationInfo.chargingStationId + '#' + connectorId.toString() + ' for idTag ' + requestPayload.idTag);
@@ -84,6 +86,8 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       return;
     }
     if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+      (this.chargingStation.getBeginEndMeterValues() && this.chargingStation.getOutOfOrderEndMeterValues())
+        && await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(transactionConnectorId, requestPayload.transactionId, requestPayload.meterStop);
       if (!this.chargingStation.isChargingStationAvailable() || !this.chargingStation.isConnectorAvailable(transactionConnectorId)) {
         await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.UNAVAILABLE);
         this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.UNAVAILABLE;
diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
new file mode 100644 (file)
index 0000000..5a2bfa9
--- /dev/null
@@ -0,0 +1,62 @@
+import { MeterValueContext, MeterValueLocation, MeterValuePhase, MeterValueUnit, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
+
+import ChargingStation from '../../ChargingStation';
+import Utils from '../../../utils/Utils';
+import logger from '../../../utils/Logger';
+
+export class OCPP16ServiceUtils {
+  public static checkMeasurandPowerDivider(chargingStation: ChargingStation, measurandType: OCPP16MeterValueMeasurand): void {
+    if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
+      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
+      logger.error(errMsg);
+      throw Error(errMsg);
+    } else if (chargingStation.stationInfo.powerDivider && chargingStation.stationInfo.powerDivider <= 0) {
+      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
+      logger.error(errMsg);
+      throw Error(errMsg);
+    }
+  }
+
+  public static buildSampledValue(sampledValueTemplate: OCPP16SampledValue, value: number, context?: MeterValueContext, phase?: MeterValuePhase): OCPP16SampledValue {
+    const sampledValueContext = context ?? (sampledValueTemplate.context ?? null);
+    const sampledValueLocation = sampledValueTemplate.location
+      ? sampledValueTemplate.location
+      : (OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand ?? null));
+    const sampledValuePhase = phase ?? (sampledValueTemplate.phase ?? null);
+    return {
+      ...!Utils.isNullOrUndefined(sampledValueTemplate.unit) && { unit: sampledValueTemplate.unit },
+      ...!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext },
+      ...!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && { measurand: sampledValueTemplate.measurand },
+      ...!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation },
+      ...!Utils.isNullOrUndefined(sampledValueTemplate.value) ? { value: sampledValueTemplate.value } : { value: value.toString() },
+      ...!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase },
+    };
+  }
+
+  public static getMeasurandDefaultUnit(measurandType: OCPP16MeterValueMeasurand): MeterValueUnit {
+    switch (measurandType) {
+      case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
+      case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
+      case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
+        return MeterValueUnit.AMP;
+      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
+      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
+        return MeterValueUnit.WATT_HOUR;
+      case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
+      case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
+      case OCPP16MeterValueMeasurand.POWER_OFFERED:
+        return MeterValueUnit.WATT;
+      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
+        return MeterValueUnit.PERCENT;
+      case OCPP16MeterValueMeasurand.VOLTAGE:
+        return MeterValueUnit.VOLT;
+    }
+  }
+
+  public static getMeasurandDefaultLocation(measurandType: OCPP16MeterValueMeasurand): MeterValueLocation {
+    switch (measurandType) {
+      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
+        return MeterValueLocation.EV;
+    }
+  }
+}
index 70742ea8cb472e304df44beb7d95964bdc597fba..17acfbf9484869a1c9074647241317c926d54a85 100644 (file)
@@ -114,5 +114,7 @@ export default abstract class OCPPRequestService {
   public abstract sendStartTransaction(connectorId: number, idTag?: string): Promise<StartTransactionResponse>;
   public abstract sendStopTransaction(transactionId: number, meterStop: number, idTag?: string, reason?: StopTransactionReason): Promise<StopTransactionResponse>;
   public abstract sendMeterValues(connectorId: number, transactionId: number, interval: number, self: OCPPRequestService): Promise<void>;
+  public abstract sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number): Promise<void>;
+  public abstract sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number): Promise<void>;
   public abstract sendError(messageId: string, error: OCPPError, commandName: RequestCommand | IncomingRequestCommand): Promise<unknown>;
 }
index dbe9b8d23a9080a8126b2b5c9bef8852f71deb51..13d8e75b07a6d1755e67584201ede4dcfd279633 100644 (file)
@@ -47,6 +47,7 @@ export default interface ChargingStationTemplate {
   powerSharedByConnectors?: boolean;
   powerUnit: PowerUnits;
   currentOutType?: CurrentOutType;
+  voltageOut?: number;
   numberOfPhases?: number;
   numberOfConnectors?: number | number[];
   useConnectorId0?: boolean;
@@ -57,7 +58,9 @@ export default interface ChargingStationTemplate {
   reconnectExponentialDelay?: boolean;
   registrationMaxRetries?: number;
   enableStatistics?: boolean;
-  voltageOut?: number;
+  beginEndMeterValues?: boolean;
+  outOfOrderEndMeterValues?: boolean;
+  meteringPerTransaction?: boolean;
   Configuration?: ChargingStationConfiguration;
   AutomaticTransactionGenerator: AutomaticTransactionGenerator;
   Connectors: Connectors;
index 38bebf336dbdd2c16c6cc567e708bbed695bc8e7..0a2a6f3949052eb23a0c47bc92a22cd2a3fdd315 100644 (file)
@@ -12,7 +12,8 @@ export interface Connector {
   transactionId?: number;
   transactionSetInterval?: NodeJS.Timeout;
   idTag?: string;
-  lastEnergyActiveImportRegisterValue?: number;
+  energyActiveImportRegisterValue?: number;
+  transactionEnergyActiveImportRegisterValue?: number;
   chargingProfiles?: ChargingProfile[]
 }