From d3195f0a57d61e2afd211f30092f8f1afe6c5245 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 9 Sep 2022 00:13:44 +0200 Subject: [PATCH] UI protocol: add meter values command MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Reference #169 Signed-off-by: Jérôme Benoit --- .../ChargingStationWorkerBroadcastChannel.ts | 94 ++++++++++++++----- .../ocpp/1.6/OCPP16ResponseService.ts | 3 +- .../ui-services/AbstractUIService.ts | 2 +- .../ui-server/ui-services/UIService001.ts | 45 +++++---- src/types/UIProtocol.ts | 1 + src/types/WorkerBroadcastChannel.ts | 1 + src/utils/Constants.ts | 1 + 7 files changed, 103 insertions(+), 44 deletions(-) diff --git a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts index 99b16e65..cf089ee3 100644 --- a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts @@ -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; } diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 89f8ffd0..8f8d0bae 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -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( diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 6d285926..c89601f6 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -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); } } } diff --git a/src/charging-station/ui-server/ui-services/UIService001.ts b/src/charging-station/ui-server/ui-services/UIService001.ts index 28286d3b..ddbd54e9 100644 --- a/src/charging-station/ui-server/ui-services/UIService001.ts +++ b/src/charging-station/ui-server/ui-services/UIService001.ts @@ -8,26 +8,27 @@ import { BroadcastChannelProcedureName } from '../../../types/WorkerBroadcastCha import type { AbstractUIServer } from '../AbstractUIServer'; import AbstractUIService from './AbstractUIService'; -const ProcedureNameToBroadCastChannelProcedureNameMap: Omit< - Record, - '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, + '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 diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index 6468290d..8195f7be 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -42,6 +42,7 @@ export enum ProcedureName { AUTHORIZE = 'authorize', STATUS_NOTIFICATION = 'statusNotification', HEARTBEAT = 'heartbeat', + METER_VALUES = 'meterValues', } export interface RequestPayload extends JsonObject { diff --git a/src/types/WorkerBroadcastChannel.ts b/src/types/WorkerBroadcastChannel.ts index e7017356..8b0d643e 100644 --- a/src/types/WorkerBroadcastChannel.ts +++ b/src/types/WorkerBroadcastChannel.ts @@ -19,6 +19,7 @@ export enum BroadcastChannelProcedureName { AUTHORIZE = 'authorize', STATUS_NOTIFICATION = 'statusNotification', HEARTBEAT = 'heartbeat', + METER_VALUES = 'meterValues', } export interface BroadcastChannelRequestPayload extends RequestPayload { diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index ee8bcb41..b2867425 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -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, -- 2.34.1