X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Fbroadcast-channel%2FChargingStationWorkerBroadcastChannel.ts;h=b5fc1cef13cc76b3f8e5250227485594c55e4210;hb=a575d48dc3c981068af8111ac4604cc6e29b6d6b;hp=b2828305625a3de2428cfedc23b150c2549511ff;hpb=7671fa0be211e944f027ebd83f3a0ad64c2ef2d6;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts index b2828305..b5fc1cef 100644 --- a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts @@ -1,5 +1,7 @@ -import { WorkerBroadcastChannel } from './WorkerBroadcastChannel'; -import { BaseError, type OCPPError } from '../../exception'; +import { secondsToMilliseconds } from 'date-fns' +import { isEmpty } from 'rambda' + +import { BaseError, type OCPPError } from '../../exception/index.js' import { AuthorizationStatus, type AuthorizeRequest, @@ -15,6 +17,7 @@ import { DataTransferStatus, type DiagnosticsStatusNotificationRequest, type DiagnosticsStatusNotificationResponse, + type EmptyObject, type FirmwareStatusNotificationRequest, type FirmwareStatusNotificationResponse, type HeartbeatRequest, @@ -32,332 +35,356 @@ import { type StatusNotificationRequest, type StatusNotificationResponse, type StopTransactionRequest, - type StopTransactionResponse, -} from '../../types'; -import { Constants, Utils, logger } from '../../utils'; -import type { ChargingStation } from '../ChargingStation'; -import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils'; -import { OCPP16ServiceUtils } from '../ocpp'; + type StopTransactionResponse +} from '../../types/index.js' +import { Constants, convertToInt, isAsyncFunction, logger } from '../../utils/index.js' +import type { ChargingStation } from '../ChargingStation.js' +import { getConfigurationKey } from '../ConfigurationKeyUtils.js' +import { buildMeterValue } from '../ocpp/index.js' +import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js' -const moduleName = 'ChargingStationWorkerBroadcastChannel'; +const moduleName = 'ChargingStationWorkerBroadcastChannel' type CommandResponse = + | EmptyObject | StartTransactionResponse | StopTransactionResponse | AuthorizeResponse | BootNotificationResponse - | StatusNotificationResponse | HeartbeatResponse - | MeterValuesResponse | DataTransferResponse - | DiagnosticsStatusNotificationResponse - | FirmwareStatusNotificationResponse; type CommandHandler = ( requestPayload?: BroadcastChannelRequestPayload -) => Promise | void; + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type +) => Promise | CommandResponse | void export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { - private readonly commandHandlers: Map; - private readonly chargingStation: ChargingStation; + private readonly commandHandlers: Map + private readonly chargingStation: ChargingStation - constructor(chargingStation: ChargingStation) { - super(); + constructor (chargingStation: ChargingStation) { + super() const requestParams: RequestParams = { - throwError: true, - }; + throwError: true + } this.commandHandlers = new Map([ - [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()], + [ + BroadcastChannelProcedureName.START_CHARGING_STATION, + () => { + this.chargingStation.start() + } + ], [ BroadcastChannelProcedureName.STOP_CHARGING_STATION, - async () => this.chargingStation.stop(), + async () => { + await this.chargingStation.stop() + } + ], + [ + BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS, + async (requestPayload?: BroadcastChannelRequestPayload) => { + await this.chargingStation.delete(requestPayload?.deleteConfiguration as boolean) + } ], [ BroadcastChannelProcedureName.OPEN_CONNECTION, - () => this.chargingStation.openWSConnection(), + () => { + this.chargingStation.openWSConnection() + } ], [ BroadcastChannelProcedureName.CLOSE_CONNECTION, - () => this.chargingStation.closeWSConnection(), + () => { + this.chargingStation.closeWSConnection() + } ], [ BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, - (requestPayload?: BroadcastChannelRequestPayload) => - this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds), + (requestPayload?: BroadcastChannelRequestPayload) => { + this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds) + } ], [ BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, - (requestPayload?: BroadcastChannelRequestPayload) => - this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds), + (requestPayload?: BroadcastChannelRequestPayload) => { + this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds) + } ], [ BroadcastChannelProcedureName.SET_SUPERVISION_URL, - (requestPayload?: BroadcastChannelRequestPayload) => - this.chargingStation.setSupervisionUrl(requestPayload?.url as string), + (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), + await 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 + await this.chargingStation.ocppRequestService.requestHandler< + StopTransactionRequest, + StartTransactionResponse >( this.chargingStation, RequestCommand.STOP_TRANSACTION, { meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId( - requestPayload.transactionId, + requestPayload?.transactionId, true ), - ...requestPayload, + ...requestPayload }, requestParams - ), + ) ], [ BroadcastChannelProcedureName.AUTHORIZE, async (requestPayload?: BroadcastChannelRequestPayload) => - this.chargingStation.ocppRequestService.requestHandler< - AuthorizeRequest, - AuthorizeResponse - >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams), + await 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 + BootNotificationRequest, + BootNotificationResponse >( this.chargingStation, RequestCommand.BOOT_NOTIFICATION, { ...this.chargingStation.bootNotificationRequest, - ...requestPayload, + ...requestPayload }, { skipBufferingOnError: true, - throwError: true, + throwError: true } - ); - return this.chargingStation.bootNotificationResponse; - }, + ) + return this.chargingStation.bootNotificationResponse + } ], [ BroadcastChannelProcedureName.STATUS_NOTIFICATION, async (requestPayload?: BroadcastChannelRequestPayload) => - this.chargingStation.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >( - this.chargingStation, - RequestCommand.STATUS_NOTIFICATION, - requestPayload, - requestParams - ), + await 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), + await 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 + const configuredMeterValueSampleInterval = getConfigurationKey( + chargingStation, + StandardParametersKey.MeterValueSampleInterval + ) + return await this.chargingStation.ocppRequestService.requestHandler< + MeterValuesRequest, + MeterValuesResponse >( this.chargingStation, RequestCommand.METER_VALUES, { meterValue: [ - // FIXME: Implement OCPP version agnostic helpers - OCPP16ServiceUtils.buildMeterValue( + buildMeterValue( this.chargingStation, - requestPayload.connectorId, - this.chargingStation.getConnectorStatus(requestPayload.connectorId) - ?.transactionId, - configuredMeterValueSampleInterval - ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + requestPayload!.connectorId!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.chargingStation.getConnectorStatus(requestPayload!.connectorId!)! + .transactionId!, + configuredMeterValueSampleInterval != null + ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value)) : Constants.DEFAULT_METER_VALUES_INTERVAL - ), + ) ], - ...requestPayload, + ...requestPayload }, requestParams - ); - }, + ) + } ], [ BroadcastChannelProcedureName.DATA_TRANSFER, async (requestPayload?: BroadcastChannelRequestPayload) => - this.chargingStation.ocppRequestService.requestHandler< - DataTransferRequest, - DataTransferResponse - >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams), + await 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 + await 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 + await 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; + ) + ] + ]) + this.chargingStation = chargingStation + this.onmessage = this.requestHandler.bind(this) as (message: unknown) => void + this.onmessageerror = this.messageErrorHandler.bind(this) as (message: unknown) => void } - private async requestHandler(messageEvent: MessageEvent): Promise { - const validatedMessageEvent = this.validateMessageEvent(messageEvent); + private requestHandler (messageEvent: MessageEvent): void { + const validatedMessageEvent = this.validateMessageEvent(messageEvent) if (validatedMessageEvent === false) { - return; + return } - if (this.isResponse(validatedMessageEvent.data) === true) { - return; + if (this.isResponse(validatedMessageEvent.data)) { + return } - const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest; + const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest if ( - !Utils.isNullOrUndefined(requestPayload?.hashIds) && - requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false + requestPayload.hashIds != null && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + !requestPayload.hashIds.includes(this.chargingStation.stationInfo!.hashId) ) { - return; + return } - if (!Utils.isNullOrUndefined(requestPayload?.hashId)) { + if (requestPayload.hashId != null) { logger.error( `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead` - ); - return; + ) + return } - let responsePayload: BroadcastChannelResponsePayload; - let commandResponse: CommandResponse | void; - try { - commandResponse = await this.commandHandler(command, requestPayload); - if ( - Utils.isNullOrUndefined(commandResponse) || - Utils.isEmptyObject(commandResponse as CommandResponse) - ) { + let responsePayload: BroadcastChannelResponsePayload | undefined + this.commandHandler(command, requestPayload) + .then(commandResponse => { + if (commandResponse == null || isEmpty(commandResponse)) { + responsePayload = { + hashId: this.chargingStation.stationInfo?.hashId, + status: ResponseStatus.SUCCESS + } + } else { + responsePayload = this.commandResponseToResponsePayload( + command, + requestPayload, + commandResponse + ) + } + }) + .catch((error: unknown) => { + logger.error( + `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`, + error + ) responsePayload = { - hashId: this.chargingStation.stationInfo.hashId, - status: ResponseStatus.SUCCESS, - }; - } else { - responsePayload = this.commandResponseToResponsePayload( + hashId: this.chargingStation.stationInfo?.hashId, + status: ResponseStatus.FAILURE, command, requestPayload, - commandResponse as CommandResponse - ); - } - } catch (error) { - logger.error( - `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`, - error - ); - responsePayload = { - hashId: this.chargingStation.stationInfo.hashId, - status: ResponseStatus.FAILURE, - command, - requestPayload, - commandResponse: commandResponse as CommandResponse, - errorMessage: (error as Error).message, - errorStack: (error as Error).stack, - errorDetails: (error as OCPPError).details, - }; - } finally { - this.sendResponse([uuid, responsePayload]); - } + errorMessage: (error as OCPPError).message, + errorStack: (error as OCPPError).stack, + errorDetails: (error as OCPPError).details + } satisfies BroadcastChannelResponsePayload + }) + .finally(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.sendResponse([uuid, responsePayload!]) + }) } - private messageErrorHandler(messageEvent: MessageEvent): void { + private messageErrorHandler (messageEvent: MessageEvent): void { logger.error( `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`, messageEvent - ); + ) } - private async commandHandler( + private async commandHandler ( command: BroadcastChannelProcedureName, requestPayload: BroadcastChannelRequestPayload + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type ): Promise { - if (this.commandHandlers.has(command) === true) { - this.cleanRequestPayload(command, requestPayload); - return this.commandHandlers.get(command)(requestPayload); + if (this.commandHandlers.has(command)) { + this.cleanRequestPayload(command, requestPayload) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const commandHandler = this.commandHandlers.get(command)! + if (isAsyncFunction(commandHandler)) { + return await commandHandler(requestPayload) + } + return ( + commandHandler as ( + requestPayload?: BroadcastChannelRequestPayload + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + ) => CommandResponse | void + )(requestPayload) } - throw new BaseError(`Unknown worker broadcast channel command: ${command}`); + throw new BaseError(`Unknown worker broadcast channel command: '${command}'`) } - private cleanRequestPayload( + private cleanRequestPayload ( command: BroadcastChannelProcedureName, requestPayload: BroadcastChannelRequestPayload ): void { - delete requestPayload.hashId; - delete requestPayload.hashIds; - [ + delete requestPayload.hashId + delete requestPayload.hashIds + ![ BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, - BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, - ].includes(command) === false && delete requestPayload.connectorIds; + BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR + ].includes(command) && delete requestPayload.connectorIds } - private commandResponseToResponsePayload( + private commandResponseToResponsePayload ( command: BroadcastChannelProcedureName, requestPayload: BroadcastChannelRequestPayload, commandResponse: CommandResponse ): BroadcastChannelResponsePayload { - const responseStatus = this.commandResponseToResponseStatus(command, commandResponse); + const responseStatus = this.commandResponseToResponseStatus(command, commandResponse) if (responseStatus === ResponseStatus.SUCCESS) { return { - hashId: this.chargingStation.stationInfo.hashId, - status: responseStatus, - }; + hashId: this.chargingStation.stationInfo?.hashId, + status: responseStatus + } } return { - hashId: this.chargingStation.stationInfo.hashId, + hashId: this.chargingStation.stationInfo?.hashId, status: responseStatus, command, requestPayload, - commandResponse, - }; + commandResponse + } } - private commandResponseToResponseStatus( + private commandResponseToResponseStatus ( command: BroadcastChannelProcedureName, commandResponse: CommandResponse ): ResponseStatus { @@ -371,34 +398,34 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne | StartTransactionResponse | StopTransactionResponse | AuthorizeResponse - )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED + ).idTagInfo?.status === AuthorizationStatus.ACCEPTED ) { - return ResponseStatus.SUCCESS; + return ResponseStatus.SUCCESS } - return ResponseStatus.FAILURE; + return ResponseStatus.FAILURE case BroadcastChannelProcedureName.BOOT_NOTIFICATION: - if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) { - return ResponseStatus.SUCCESS; + if (commandResponse.status === RegistrationStatusEnumType.ACCEPTED) { + return ResponseStatus.SUCCESS } - return ResponseStatus.FAILURE; + return ResponseStatus.FAILURE case BroadcastChannelProcedureName.DATA_TRANSFER: - if (commandResponse?.status === DataTransferStatus.ACCEPTED) { - return ResponseStatus.SUCCESS; + if (commandResponse.status === DataTransferStatus.ACCEPTED) { + return ResponseStatus.SUCCESS } - return ResponseStatus.FAILURE; + return ResponseStatus.FAILURE case BroadcastChannelProcedureName.STATUS_NOTIFICATION: case BroadcastChannelProcedureName.METER_VALUES: - if (Utils.isEmptyObject(commandResponse) === true) { - return ResponseStatus.SUCCESS; + if (isEmpty(commandResponse)) { + return ResponseStatus.SUCCESS } - return ResponseStatus.FAILURE; + return ResponseStatus.FAILURE case BroadcastChannelProcedureName.HEARTBEAT: if ('currentTime' in commandResponse) { - return ResponseStatus.SUCCESS; + return ResponseStatus.SUCCESS } - return ResponseStatus.FAILURE; + return ResponseStatus.FAILURE default: - return ResponseStatus.FAILURE; + return ResponseStatus.FAILURE } } }