-import { BroadcastChannel } from 'worker_threads';
-
+import BaseError from '../exception/BaseError';
import { RequestCommand } from '../types/ocpp/Requests';
import {
+ AuthorizationStatus,
StartTransactionRequest,
StartTransactionResponse,
- StopTransactionReason,
StopTransactionRequest,
StopTransactionResponse,
} from '../types/ocpp/Transaction';
import {
BroadcastChannelProcedureName,
BroadcastChannelRequest,
+ BroadcastChannelRequestPayload,
+ BroadcastChannelResponsePayload,
+ MessageEvent,
} from '../types/WorkerBroadcastChannel';
-import ChargingStation from './ChargingStation';
+import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
+import logger from '../utils/Logger';
+import type ChargingStation from './ChargingStation';
+import WorkerBroadcastChannel from './WorkerBroadcastChannel';
+
+const moduleName = 'ChargingStationWorkerBroadcastChannel';
-type MessageEvent = { data: unknown };
+type CommandResponse = StartTransactionResponse | StopTransactionResponse;
-export default class ChargingStationWorkerBroadcastChannel extends BroadcastChannel {
+export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
private readonly chargingStation: ChargingStation;
constructor(chargingStation: ChargingStation) {
- super('worker');
+ super();
this.chargingStation = chargingStation;
- this.onmessage = this.handleRequest.bind(this) as (message: MessageEvent) => void;
+ this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
+ this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
}
- private async handleRequest(messageEvent: MessageEvent): Promise<void> {
- const [, command, payload] = messageEvent.data as BroadcastChannelRequest;
-
- if (payload.hashId !== this.chargingStation.hashId) {
+ private async requestHandler(messageEvent: MessageEvent): Promise<void> {
+ if (this.isResponse(messageEvent.data)) {
return;
}
+ const [uuid, command, requestPayload] = this.validateMessageEvent(messageEvent)
+ .data as BroadcastChannelRequest;
+
+ if (requestPayload?.hashIds !== undefined || requestPayload?.hashId !== undefined) {
+ if (
+ requestPayload?.hashId === undefined &&
+ requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
+ ) {
+ return;
+ }
+ if (
+ requestPayload?.hashIds === undefined &&
+ requestPayload?.hashId !== this.chargingStation.stationInfo.hashId
+ ) {
+ return;
+ }
+ if (requestPayload?.hashId !== undefined) {
+ logger.warn(
+ `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
+ );
+ }
+ }
+
+ let responsePayload: BroadcastChannelResponsePayload;
+ let commandResponse: CommandResponse;
+ try {
+ commandResponse = await this.commandHandler(command, requestPayload);
+ if (commandResponse === undefined) {
+ responsePayload = {
+ hashId: this.chargingStation.stationInfo.hashId,
+ status: ResponseStatus.SUCCESS,
+ };
+ } else {
+ responsePayload = {
+ hashId: this.chargingStation.stationInfo.hashId,
+ status: this.commandResponseToResponseStatus(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,
+ errorMessage: (error as Error).message,
+ errorStack: (error as Error).stack,
+ };
+ }
+ this.sendResponse([uuid, responsePayload]);
+ }
+
+ private messageErrorHandler(messageEvent: MessageEvent): void {
+ logger.error(
+ `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
+ { messageEvent }
+ );
+ }
- // TODO: return a response stating the command success or failure
+ private async commandHandler(
+ command: BroadcastChannelProcedureName,
+ requestPayload: BroadcastChannelRequestPayload
+ ): Promise<CommandResponse | undefined> {
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:
- await this.chargingStation.ocppRequestService.requestHandler<
+ return this.chargingStation.ocppRequestService.requestHandler<
StartTransactionRequest,
StartTransactionResponse
>(this.chargingStation, RequestCommand.START_TRANSACTION, {
- connectorId: payload.connectorId,
- idTag: payload.idTag,
+ connectorId: requestPayload.connectorId,
+ idTag: requestPayload.idTag,
});
- break;
case BroadcastChannelProcedureName.STOP_TRANSACTION:
- await this.chargingStation.ocppRequestService.requestHandler<
+ return this.chargingStation.ocppRequestService.requestHandler<
StopTransactionRequest,
StopTransactionResponse
>(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
- transactionId: payload.transactionId,
+ transactionId: requestPayload.transactionId,
meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
- payload.transactionId
+ requestPayload.transactionId,
+ true
),
- idTag: this.chargingStation.getTransactionIdTag(payload.transactionId),
- reason: StopTransactionReason.NONE,
+ idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
+ ...(requestPayload.reason && { reason: requestPayload.reason }),
});
+ case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
+ this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds);
break;
- case BroadcastChannelProcedureName.START_CHARGING_STATION:
- this.chargingStation.start();
- break;
- case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
- await this.chargingStation.stop();
+ case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR:
+ this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds);
break;
+ default:
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
+ }
+ }
+
+ private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
+ if (commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
+ return ResponseStatus.SUCCESS;
}
+ return ResponseStatus.FAILURE;
}
}