UI protocol: add meter values command
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 8 Sep 2022 22:13:44 +0000 (00:13 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 8 Sep 2022 22:13:44 +0000 (00:13 +0200)
Reference #169

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/charging-station/ui-server/ui-services/UIService001.ts
src/types/UIProtocol.ts
src/types/WorkerBroadcastChannel.ts
src/utils/Constants.ts

index 99b16e655923cfca003882e5e4003b0e1e45a327..cf089ee3919b54f8a77f16b77b27d0674ea4cf07 100644 (file)
@@ -1,11 +1,17 @@
 import BaseError from '../exception/BaseError';
 import type OCPPError from '../exception/OCPPError';
+import { StandardParametersKey } from '../types/ocpp/Configuration';
 import {
   HeartbeatRequest,
+  MeterValuesRequest,
   RequestCommand,
   type StatusNotificationRequest,
 } from '../types/ocpp/Requests';
-import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses';
+import type {
+  HeartbeatResponse,
+  MeterValuesResponse,
+  StatusNotificationResponse,
+} from '../types/ocpp/Responses';
 import {
   AuthorizationStatus,
   AuthorizeRequest,
@@ -23,9 +29,12 @@ import {
   MessageEvent,
 } from '../types/WorkerBroadcastChannel';
 import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
+import Constants from '../utils/Constants';
 import logger from '../utils/Logger';
 import Utils from '../utils/Utils';
 import type ChargingStation from './ChargingStation';
+import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
+import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
 
 const moduleName = 'ChargingStationWorkerBroadcastChannel';
@@ -35,7 +44,8 @@ type CommandResponse =
   | StopTransactionResponse
   | AuthorizeResponse
   | StatusNotificationResponse
-  | HeartbeatResponse;
+  | HeartbeatResponse
+  | MeterValuesResponse;
 
 type CommandHandler = (
   requestPayload?: BroadcastChannelRequestPayload
@@ -88,10 +98,12 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
             StartTransactionResponse
           >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
             ...requestPayload,
-            meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
-              requestPayload.transactionId,
-              true
-            ),
+            meterStop:
+              requestPayload.meterStop ??
+              this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
+                requestPayload.transactionId,
+                true
+              ),
           }),
       ],
       [
@@ -118,6 +130,32 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
             HeartbeatResponse
           >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload),
       ],
+      [
+        BroadcastChannelProcedureName.METER_VALUES,
+        async (requestPayload?: BroadcastChannelRequestPayload) => {
+          const configuredMeterValueSampleInterval =
+            ChargingStationConfigurationUtils.getConfigurationKey(
+              chargingStation,
+              StandardParametersKey.MeterValueSampleInterval
+            );
+          return this.chargingStation.ocppRequestService.requestHandler<
+            MeterValuesRequest,
+            MeterValuesResponse
+          >(this.chargingStation, RequestCommand.METER_VALUES, {
+            ...requestPayload,
+            meterValue: requestPayload.meterValue ?? [
+              OCPP16ServiceUtils.buildMeterValue(
+                this.chargingStation,
+                requestPayload.connectorId,
+                this.chargingStation.getConnectorStatus(requestPayload.connectorId)?.transactionId,
+                configuredMeterValueSampleInterval
+                  ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
+                  : Constants.DEFAULT_METER_VALUES_INTERVAL
+              ),
+            ],
+          });
+        },
+      ],
     ]);
     this.chargingStation = chargingStation;
     this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
@@ -156,24 +194,11 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
           status: ResponseStatus.SUCCESS,
         };
       } else {
-        const commandResponseStatus = this.commandResponseToResponseStatus(
+        responsePayload = this.commandResponseToResponsePayload(
           command,
+          requestPayload,
           commandResponse as CommandResponse
         );
-        if (commandResponseStatus === ResponseStatus.SUCCESS) {
-          responsePayload = {
-            hashId: this.chargingStation.stationInfo.hashId,
-            status: commandResponseStatus,
-          };
-        } else {
-          responsePayload = {
-            hashId: this.chargingStation.stationInfo.hashId,
-            status: commandResponseStatus,
-            command,
-            requestPayload,
-            commandResponse: commandResponse as CommandResponse,
-          };
-        }
       }
     } catch (error) {
       logger.error(
@@ -225,7 +250,31 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
     ].includes(command) === false && delete requestPayload.connectorIds;
   }
 
-  private commandResponseToResponseStatus(
+  private commandResponseToResponsePayload(
+    command: BroadcastChannelProcedureName,
+    requestPayload: BroadcastChannelRequestPayload,
+    commandResponse: CommandResponse
+  ): BroadcastChannelResponsePayload {
+    const commandResponseStatus = this.commandResponseStatusToResponseStatus(
+      command,
+      commandResponse
+    );
+    if (commandResponseStatus === ResponseStatus.SUCCESS) {
+      return {
+        hashId: this.chargingStation.stationInfo.hashId,
+        status: commandResponseStatus,
+      };
+    }
+    return {
+      hashId: this.chargingStation.stationInfo.hashId,
+      status: commandResponseStatus,
+      command,
+      requestPayload,
+      commandResponse,
+    };
+  }
+
+  private commandResponseStatusToResponseStatus(
     command: BroadcastChannelProcedureName,
     commandResponse: CommandResponse
   ): ResponseStatus {
@@ -245,6 +294,7 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
         }
         return ResponseStatus.FAILURE;
       case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
+      case BroadcastChannelProcedureName.METER_VALUES:
         if (Utils.isEmptyObject(commandResponse) === true) {
           return ResponseStatus.SUCCESS;
         }
index 89f8ffd076a43f9e646f8c93549940cdd081cbe1..8f8d0bae9f6a8be426fee0d2f20519a3795b6af0 100644 (file)
@@ -38,6 +38,7 @@ import {
 } from '../../../types/ocpp/1.6/Transaction';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
 import type { ResponseHandler } from '../../../types/ocpp/Responses';
+import Constants from '../../../utils/Constants';
 import logger from '../../../utils/Logger';
 import Utils from '../../../utils/Utils';
 import type ChargingStation from '../../ChargingStation';
@@ -485,7 +486,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         connectorId,
         configuredMeterValueSampleInterval
           ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
-          : 60000
+          : Constants.DEFAULT_METER_VALUES_INTERVAL
       );
     } else {
       logger.warn(
index 6d285926e14593b1fb248fa09583b8e6dae5f0c5..c89601f6dd4c2cff0b83a2c9c5e353fd6c34cb5b 100644 (file)
@@ -76,7 +76,7 @@ export default abstract class AbstractUIService {
     } finally {
       // Send response for payload not forwarded to broadcast channel
       if (responsePayload !== undefined) {
-        this.sendResponse(messageId ?? 'error', responsePayload);
+        this.sendResponse(messageId, responsePayload);
       }
     }
   }
index 28286d3be62c03b702a5488043b5e4984acc4138..ddbd54e9250ba9d4d4d3d27c0bdffddaa3692ab6 100644 (file)
@@ -8,26 +8,27 @@ import { BroadcastChannelProcedureName } from '../../../types/WorkerBroadcastCha
 import type { AbstractUIServer } from '../AbstractUIServer';
 import AbstractUIService from './AbstractUIService';
 
-const ProcedureNameToBroadCastChannelProcedureNameMap: Omit<
-  Record<ProcedureName, BroadcastChannelProcedureName>,
-  'startSimulator' | 'stopSimulator' | 'listChargingStations'
-> = {
-  [ProcedureName.START_CHARGING_STATION]: BroadcastChannelProcedureName.START_CHARGING_STATION,
-  [ProcedureName.STOP_CHARGING_STATION]: BroadcastChannelProcedureName.STOP_CHARGING_STATION,
-  [ProcedureName.CLOSE_CONNECTION]: BroadcastChannelProcedureName.CLOSE_CONNECTION,
-  [ProcedureName.OPEN_CONNECTION]: BroadcastChannelProcedureName.OPEN_CONNECTION,
-  [ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR]:
-    BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
-  [ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR]:
-    BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
-  [ProcedureName.START_TRANSACTION]: BroadcastChannelProcedureName.START_TRANSACTION,
-  [ProcedureName.STOP_TRANSACTION]: BroadcastChannelProcedureName.STOP_TRANSACTION,
-  [ProcedureName.AUTHORIZE]: BroadcastChannelProcedureName.AUTHORIZE,
-  [ProcedureName.STATUS_NOTIFICATION]: BroadcastChannelProcedureName.STATUS_NOTIFICATION,
-  [ProcedureName.HEARTBEAT]: BroadcastChannelProcedureName.HEARTBEAT,
-};
-
 export default class UIService001 extends AbstractUIService {
+  private static readonly ProcedureNameToBroadCastChannelProcedureNameMap: Omit<
+    Record<ProcedureName, BroadcastChannelProcedureName>,
+    'startSimulator' | 'stopSimulator' | 'listChargingStations'
+  > = {
+    [ProcedureName.START_CHARGING_STATION]: BroadcastChannelProcedureName.START_CHARGING_STATION,
+    [ProcedureName.STOP_CHARGING_STATION]: BroadcastChannelProcedureName.STOP_CHARGING_STATION,
+    [ProcedureName.CLOSE_CONNECTION]: BroadcastChannelProcedureName.CLOSE_CONNECTION,
+    [ProcedureName.OPEN_CONNECTION]: BroadcastChannelProcedureName.OPEN_CONNECTION,
+    [ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR]:
+      BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
+    [ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR]:
+      BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
+    [ProcedureName.START_TRANSACTION]: BroadcastChannelProcedureName.START_TRANSACTION,
+    [ProcedureName.STOP_TRANSACTION]: BroadcastChannelProcedureName.STOP_TRANSACTION,
+    [ProcedureName.AUTHORIZE]: BroadcastChannelProcedureName.AUTHORIZE,
+    [ProcedureName.STATUS_NOTIFICATION]: BroadcastChannelProcedureName.STATUS_NOTIFICATION,
+    [ProcedureName.HEARTBEAT]: BroadcastChannelProcedureName.HEARTBEAT,
+    [ProcedureName.METER_VALUES]: BroadcastChannelProcedureName.METER_VALUES,
+  };
+
   constructor(uiServer: AbstractUIServer) {
     super(uiServer, ProtocolVersion['0.0.1']);
     this.requestHandlers.set(
@@ -74,6 +75,10 @@ export default class UIService001 extends AbstractUIService {
       ProcedureName.HEARTBEAT,
       this.handleProtocolRequest.bind(this) as ProtocolRequestHandler
     );
+    this.requestHandlers.set(
+      ProcedureName.METER_VALUES,
+      this.handleProtocolRequest.bind(this) as ProtocolRequestHandler
+    );
   }
 
   private handleProtocolRequest(
@@ -83,7 +88,7 @@ export default class UIService001 extends AbstractUIService {
   ): void {
     this.sendBroadcastChannelRequest(
       uuid,
-      ProcedureNameToBroadCastChannelProcedureNameMap[
+      UIService001.ProcedureNameToBroadCastChannelProcedureNameMap[
         procedureName
       ] as BroadcastChannelProcedureName,
       payload
index 6468290d59b8cd16c61f2ed990a6df44fee0832a..8195f7be7086722dbf386aa587abb3f4e30939d5 100644 (file)
@@ -42,6 +42,7 @@ export enum ProcedureName {
   AUTHORIZE = 'authorize',
   STATUS_NOTIFICATION = 'statusNotification',
   HEARTBEAT = 'heartbeat',
+  METER_VALUES = 'meterValues',
 }
 
 export interface RequestPayload extends JsonObject {
index e7017356162205f91f4152a1862923cd7f06a4a0..8b0d643ee3f8300f9db91f50f8ce6769547e5fb9 100644 (file)
@@ -19,6 +19,7 @@ export enum BroadcastChannelProcedureName {
   AUTHORIZE = 'authorize',
   STATUS_NOTIFICATION = 'statusNotification',
   HEARTBEAT = 'heartbeat',
+  METER_VALUES = 'meterValues',
 }
 
 export interface BroadcastChannelRequestPayload extends RequestPayload {
index ee8bcb41c8abe5aabdcf383396d7bc4f343e45e7..b28674259454fce95bf2e37719a1c2e15ee4719d 100644 (file)
@@ -101,6 +101,7 @@ export default class Constants {
   static readonly DEFAULT_LOG_STATISTICS_INTERVAL = 60; // Seconds
 
   static readonly DEFAULT_HEARTBEAT_INTERVAL = 60000; // Ms
+  static readonly DEFAULT_METER_VALUES_INTERVAL = 60000; // Ms
 
   static readonly SUPPORTED_MEASURANDS = Object.freeze([
     MeterValueMeasurand.STATE_OF_CHARGE,