UI protocol: Allow to send some relevant commands to several charging… (#152)
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
1 import BaseError from '../exception/BaseError';
2 import { RequestCommand } from '../types/ocpp/Requests';
3 import {
4 AuthorizationStatus,
5 StartTransactionRequest,
6 StartTransactionResponse,
7 StopTransactionReason,
8 StopTransactionRequest,
9 StopTransactionResponse,
10 } from '../types/ocpp/Transaction';
11 import {
12 BroadcastChannelProcedureName,
13 BroadcastChannelRequest,
14 BroadcastChannelRequestPayload,
15 BroadcastChannelResponsePayload,
16 MessageEvent,
17 } from '../types/WorkerBroadcastChannel';
18 import { ResponseStatus } from '../ui/web/src/type/UIProtocol';
19 import logger from '../utils/Logger';
20 import type ChargingStation from './ChargingStation';
21 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
22
23 const moduleName = 'ChargingStationWorkerBroadcastChannel';
24
25 type CommandResponse = StartTransactionResponse | StopTransactionResponse;
26
27 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
28 private readonly chargingStation: ChargingStation;
29
30 constructor(chargingStation: ChargingStation) {
31 super();
32 this.chargingStation = chargingStation;
33 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
34 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
35 }
36
37 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
38 if (this.isResponse(messageEvent.data)) {
39 return;
40 }
41 if (Array.isArray(messageEvent.data) === false) {
42 throw new BaseError('Worker broadcast channel protocol request is not an array');
43 }
44
45 const [uuid, command, requestPayload] = messageEvent.data as BroadcastChannelRequest;
46
47 if (
48 requestPayload?.hashId === undefined &&
49 (requestPayload?.hashIds as string[])?.includes(this.chargingStation.hashId) === false
50 ) {
51 return;
52 }
53 if (
54 requestPayload?.hashIds === undefined &&
55 requestPayload?.hashId !== this.chargingStation.hashId
56 ) {
57 return;
58 }
59 if (requestPayload?.hashId !== undefined) {
60 logger.warn(
61 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
62 );
63 }
64
65 let responsePayload: BroadcastChannelResponsePayload;
66 let commandResponse: CommandResponse;
67 try {
68 commandResponse = await this.commandHandler(command, requestPayload);
69 if (commandResponse === undefined) {
70 responsePayload = { status: ResponseStatus.SUCCESS };
71 } else {
72 responsePayload = { status: this.commandResponseToResponseStatus(commandResponse) };
73 }
74 } catch (error) {
75 logger.error(
76 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
77 error
78 );
79 responsePayload = {
80 status: ResponseStatus.FAILURE,
81 command,
82 requestPayload,
83 commandResponse,
84 errorMessage: (error as Error).message,
85 errorStack: (error as Error).stack,
86 };
87 }
88 this.sendResponse([uuid, responsePayload]);
89 }
90
91 private messageErrorHandler(messageEvent: MessageEvent): void {
92 logger.error(
93 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
94 { messageEvent, messageEventData: messageEvent.data }
95 );
96 }
97
98 private async commandHandler(
99 command: BroadcastChannelProcedureName,
100 requestPayload: BroadcastChannelRequestPayload
101 ): Promise<CommandResponse | undefined> {
102 switch (command) {
103 case BroadcastChannelProcedureName.START_TRANSACTION:
104 return this.chargingStation.ocppRequestService.requestHandler<
105 StartTransactionRequest,
106 StartTransactionResponse
107 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
108 connectorId: requestPayload.connectorId,
109 idTag: requestPayload.idTag,
110 });
111 case BroadcastChannelProcedureName.STOP_TRANSACTION:
112 return this.chargingStation.ocppRequestService.requestHandler<
113 StopTransactionRequest,
114 StopTransactionResponse
115 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
116 transactionId: requestPayload.transactionId,
117 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
118 requestPayload.transactionId
119 ),
120 idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
121 reason: StopTransactionReason.NONE,
122 });
123 case BroadcastChannelProcedureName.START_CHARGING_STATION:
124 this.chargingStation.start();
125 break;
126 case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
127 await this.chargingStation.stop();
128 break;
129 case BroadcastChannelProcedureName.OPEN_CONNECTION:
130 this.chargingStation.openWSConnection();
131 break;
132 case BroadcastChannelProcedureName.CLOSE_CONNECTION:
133 this.chargingStation.closeWSConnection();
134 break;
135 default:
136 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
137 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
138 }
139 }
140
141 private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
142 if (commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
143 return ResponseStatus.SUCCESS;
144 }
145 return ResponseStatus.FAILURE;
146 }
147 }