X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationWorkerBroadcastChannel.ts;h=49ce0811d534be1e339c466a2b839132749235e3;hb=aaf2bf9c2ec7712394d7ab9da12449262d894e60;hp=3000626eced8cbd712fc6e047082cd9d54753a18;hpb=2afb4d15045cdb5f521a5d57859ba23505afc852;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts index 3000626e..49ce0811 100644 --- a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts @@ -1,56 +1,262 @@ -import BaseError from '../exception/BaseError'; -import type OCPPError from '../exception/OCPPError'; import { - HeartbeatRequest, - RequestCommand, - type StatusNotificationRequest, -} from '../types/ocpp/Requests'; -import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses'; + type ChargingStation, + ChargingStationConfigurationUtils, + WorkerBroadcastChannel, +} from './internal'; +import { OCPP16ServiceUtils } from './ocpp'; +import { BaseError, type OCPPError } from '../exception'; import { AuthorizationStatus, - StartTransactionRequest, - StartTransactionResponse, - StopTransactionRequest, - StopTransactionResponse, -} from '../types/ocpp/Transaction'; -import { + type AuthorizeRequest, + type AuthorizeResponse, + type BootNotificationRequest, + type BootNotificationResponse, BroadcastChannelProcedureName, - BroadcastChannelRequest, - BroadcastChannelRequestPayload, - BroadcastChannelResponsePayload, - MessageEvent, -} from '../types/WorkerBroadcastChannel'; -import { ResponseStatus } from '../ui/web/src/types/UIProtocol'; -import logger from '../utils/Logger'; -import Utils from '../utils/Utils'; -import type ChargingStation from './ChargingStation'; -import WorkerBroadcastChannel from './WorkerBroadcastChannel'; + type BroadcastChannelRequest, + type BroadcastChannelRequestPayload, + type BroadcastChannelResponsePayload, + type DataTransferRequest, + type DataTransferResponse, + DataTransferStatus, + type DiagnosticsStatusNotificationRequest, + type DiagnosticsStatusNotificationResponse, + type FirmwareStatusNotificationRequest, + type FirmwareStatusNotificationResponse, + type HeartbeatRequest, + type HeartbeatResponse, + type MessageEvent, + type MeterValuesRequest, + type MeterValuesResponse, + RegistrationStatusEnumType, + RequestCommand, + type RequestParams, + ResponseStatus, + StandardParametersKey, + type StartTransactionRequest, + type StartTransactionResponse, + type StatusNotificationRequest, + type StatusNotificationResponse, + type StopTransactionRequest, + type StopTransactionResponse, +} from '../types'; +import { Constants, Utils, logger } from '../utils'; const moduleName = 'ChargingStationWorkerBroadcastChannel'; type CommandResponse = | StartTransactionResponse | StopTransactionResponse + | AuthorizeResponse + | BootNotificationResponse | StatusNotificationResponse - | HeartbeatResponse; + | HeartbeatResponse + | MeterValuesResponse + | DataTransferResponse + | DiagnosticsStatusNotificationResponse + | FirmwareStatusNotificationResponse; + +type CommandHandler = ( + requestPayload?: BroadcastChannelRequestPayload +) => Promise | void; -export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { +export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { + private readonly commandHandlers: Map; private readonly chargingStation: ChargingStation; constructor(chargingStation: ChargingStation) { super(); + const requestParams: RequestParams = { + throwError: true, + }; + this.commandHandlers = new Map([ + [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()], + [ + BroadcastChannelProcedureName.STOP_CHARGING_STATION, + async () => this.chargingStation.stop(), + ], + [ + BroadcastChannelProcedureName.OPEN_CONNECTION, + () => this.chargingStation.openWSConnection(), + ], + [ + BroadcastChannelProcedureName.CLOSE_CONNECTION, + () => this.chargingStation.closeWSConnection(), + ], + [ + BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, + (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds), + ], + [ + BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, + (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds), + ], + [ + BroadcastChannelProcedureName.SET_SUPERVISION_URL, + (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.setSupervisionUrl(requestPayload?.url as string), + ], + [ + BroadcastChannelProcedureName.START_TRANSACTION, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + StartTransactionRequest, + StartTransactionResponse + >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload, requestParams), + ], + [ + BroadcastChannelProcedureName.STOP_TRANSACTION, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + StopTransactionRequest, + StartTransactionResponse + >( + this.chargingStation, + RequestCommand.STOP_TRANSACTION, + { + meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId( + requestPayload.transactionId, + true + ), + ...requestPayload, + }, + requestParams + ), + ], + [ + BroadcastChannelProcedureName.AUTHORIZE, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + AuthorizeRequest, + AuthorizeResponse + >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams), + ], + [ + BroadcastChannelProcedureName.BOOT_NOTIFICATION, + async (requestPayload?: BroadcastChannelRequestPayload) => { + this.chargingStation.bootNotificationResponse = + await this.chargingStation.ocppRequestService.requestHandler< + BootNotificationRequest, + BootNotificationResponse + >( + this.chargingStation, + RequestCommand.BOOT_NOTIFICATION, + { + ...this.chargingStation.bootNotificationRequest, + ...requestPayload, + }, + { + skipBufferingOnError: true, + throwError: true, + } + ); + return this.chargingStation.bootNotificationResponse; + }, + ], + [ + BroadcastChannelProcedureName.STATUS_NOTIFICATION, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + StatusNotificationRequest, + StatusNotificationResponse + >( + this.chargingStation, + RequestCommand.STATUS_NOTIFICATION, + requestPayload, + requestParams + ), + ], + [ + BroadcastChannelProcedureName.HEARTBEAT, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + HeartbeatRequest, + HeartbeatResponse + >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams), + ], + [ + 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, + { + meterValue: [ + // FIXME: Implement OCPP version agnostic helpers + OCPP16ServiceUtils.buildMeterValue( + this.chargingStation, + requestPayload.connectorId, + this.chargingStation.getConnectorStatus(requestPayload.connectorId) + ?.transactionId, + configuredMeterValueSampleInterval + ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000 + : Constants.DEFAULT_METER_VALUES_INTERVAL + ), + ], + ...requestPayload, + }, + requestParams + ); + }, + ], + [ + BroadcastChannelProcedureName.DATA_TRANSFER, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + DataTransferRequest, + DataTransferResponse + >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams), + ], + [ + BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + DiagnosticsStatusNotificationRequest, + DiagnosticsStatusNotificationResponse + >( + this.chargingStation, + RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, + requestPayload, + requestParams + ), + ], + [ + BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION, + async (requestPayload?: BroadcastChannelRequestPayload) => + this.chargingStation.ocppRequestService.requestHandler< + FirmwareStatusNotificationRequest, + FirmwareStatusNotificationResponse + >( + this.chargingStation, + RequestCommand.FIRMWARE_STATUS_NOTIFICATION, + requestPayload, + requestParams + ), + ], + ]); this.chargingStation = chargingStation; this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void; this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void; } private async requestHandler(messageEvent: MessageEvent): Promise { - if (this.isResponse(messageEvent.data) === true) { + const validatedMessageEvent = this.validateMessageEvent(messageEvent); + if (validatedMessageEvent === false) { return; } - const [uuid, command, requestPayload] = this.validateMessageEvent(messageEvent) - .data as BroadcastChannelRequest; - + if (this.isResponse(validatedMessageEvent.data) === true) { + return; + } + const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest; if ( requestPayload?.hashIds !== undefined && requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false @@ -59,25 +265,29 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca } if (requestPayload?.hashId !== undefined) { logger.error( - `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead` + `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead` ); return; } - let responsePayload: BroadcastChannelResponsePayload; - let commandResponse: CommandResponse; + let commandResponse: CommandResponse | void; try { commandResponse = await this.commandHandler(command, requestPayload); - if (commandResponse === undefined) { + if ( + commandResponse === undefined || + commandResponse === null || + Utils.isEmptyObject(commandResponse as CommandResponse) + ) { responsePayload = { hashId: this.chargingStation.stationInfo.hashId, status: ResponseStatus.SUCCESS, }; } else { - responsePayload = { - hashId: this.chargingStation.stationInfo.hashId, - status: this.commandResponseToResponseStatus(command, commandResponse), - }; + responsePayload = this.commandResponseToResponsePayload( + command, + requestPayload, + commandResponse as CommandResponse + ); } } catch (error) { logger.error( @@ -89,93 +299,65 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca status: ResponseStatus.FAILURE, command, requestPayload, - commandResponse, + commandResponse: commandResponse as CommandResponse, errorMessage: (error as Error).message, errorStack: (error as Error).stack, errorDetails: (error as OCPPError).details, }; + } finally { + this.sendResponse([uuid, responsePayload]); } - this.sendResponse([uuid, responsePayload]); } private messageErrorHandler(messageEvent: MessageEvent): void { logger.error( `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`, - { messageEvent } + messageEvent ); } private async commandHandler( command: BroadcastChannelProcedureName, requestPayload: BroadcastChannelRequestPayload - ): Promise { - switch (command) { - case BroadcastChannelProcedureName.START_CHARGING_STATION: - this.chargingStation.start(); - break; - case BroadcastChannelProcedureName.STOP_CHARGING_STATION: - await this.chargingStation.stop(); - break; - case BroadcastChannelProcedureName.OPEN_CONNECTION: - this.chargingStation.openWSConnection(); - break; - case BroadcastChannelProcedureName.CLOSE_CONNECTION: - this.chargingStation.closeWSConnection(); - break; - case BroadcastChannelProcedureName.START_TRANSACTION: - return this.chargingStation.ocppRequestService.requestHandler< - StartTransactionRequest, - StartTransactionResponse - >(this.chargingStation, RequestCommand.START_TRANSACTION, { - connectorId: requestPayload.connectorId, - idTag: requestPayload.idTag, - }); - case BroadcastChannelProcedureName.STOP_TRANSACTION: - return this.chargingStation.ocppRequestService.requestHandler< - StopTransactionRequest, - StopTransactionResponse - >(this.chargingStation, RequestCommand.STOP_TRANSACTION, { - transactionId: requestPayload.transactionId, - meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId( - requestPayload.transactionId, - true - ), - idTag: requestPayload.idTag, - reason: requestPayload.reason, - }); - case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR: - this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds); - break; - case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR: - this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds); - break; - case BroadcastChannelProcedureName.STATUS_NOTIFICATION: - return this.chargingStation.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, { - connectorId: requestPayload.connectorId, - errorCode: requestPayload.errorCode, - status: requestPayload.status, - ...(requestPayload.info && { info: requestPayload.info }), - ...(requestPayload.timestamp && { timestamp: requestPayload.timestamp }), - ...(requestPayload.vendorId && { vendorId: requestPayload.vendorId }), - ...(requestPayload.vendorErrorCode && { - vendorErrorCode: requestPayload.vendorErrorCode, - }), - }); - case BroadcastChannelProcedureName.HEARTBEAT: - delete requestPayload.hashId; - delete requestPayload.hashIds; - delete requestPayload.connectorIds; - return this.chargingStation.ocppRequestService.requestHandler< - HeartbeatRequest, - HeartbeatResponse - >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload); - default: - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - throw new BaseError(`Unknown worker broadcast channel command: ${command}`); + ): Promise { + if (this.commandHandlers.has(command) === true) { + this.cleanRequestPayload(command, requestPayload); + return this.commandHandlers.get(command)(requestPayload); + } + throw new BaseError(`Unknown worker broadcast channel command: ${command}`); + } + + private cleanRequestPayload( + command: BroadcastChannelProcedureName, + requestPayload: BroadcastChannelRequestPayload + ): void { + delete requestPayload.hashId; + delete requestPayload.hashIds; + [ + BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, + BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, + ].includes(command) === false && delete requestPayload.connectorIds; + } + + private commandResponseToResponsePayload( + command: BroadcastChannelProcedureName, + requestPayload: BroadcastChannelRequestPayload, + commandResponse: CommandResponse + ): BroadcastChannelResponsePayload { + const responseStatus = this.commandResponseToResponseStatus(command, commandResponse); + if (responseStatus === ResponseStatus.SUCCESS) { + return { + hashId: this.chargingStation.stationInfo.hashId, + status: responseStatus, + }; } + return { + hashId: this.chargingStation.stationInfo.hashId, + status: responseStatus, + command, + requestPayload, + commandResponse, + }; } private commandResponseToResponseStatus( @@ -185,14 +367,30 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca switch (command) { case BroadcastChannelProcedureName.START_TRANSACTION: case BroadcastChannelProcedureName.STOP_TRANSACTION: + case BroadcastChannelProcedureName.AUTHORIZE: if ( - (commandResponse as StartTransactionResponse | StopTransactionResponse)?.idTagInfo - ?.status === AuthorizationStatus.ACCEPTED + ( + commandResponse as + | StartTransactionResponse + | StopTransactionResponse + | AuthorizeResponse + )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED ) { return ResponseStatus.SUCCESS; } return ResponseStatus.FAILURE; + case BroadcastChannelProcedureName.BOOT_NOTIFICATION: + if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) { + return ResponseStatus.SUCCESS; + } + return ResponseStatus.FAILURE; + case BroadcastChannelProcedureName.DATA_TRANSFER: + if (commandResponse?.status === DataTransferStatus.ACCEPTED) { + return ResponseStatus.SUCCESS; + } + return ResponseStatus.FAILURE; case BroadcastChannelProcedureName.STATUS_NOTIFICATION: + case BroadcastChannelProcedureName.METER_VALUES: if (Utils.isEmptyObject(commandResponse) === true) { return ResponseStatus.SUCCESS; }