Commit | Line | Data |
---|---|---|
6c8f5d90 | 1 | import BaseError from '../exception/BaseError'; |
a9ed42b2 | 2 | import type OCPPError from '../exception/OCPPError'; |
10db00b2 JB |
3 | import { |
4 | HeartbeatRequest, | |
5 | RequestCommand, | |
6 | type StatusNotificationRequest, | |
7 | } from '../types/ocpp/Requests'; | |
8 | import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses'; | |
89b7a234 | 9 | import { |
6c8f5d90 | 10 | AuthorizationStatus, |
1984f194 JB |
11 | AuthorizeRequest, |
12 | AuthorizeResponse, | |
89b7a234 JB |
13 | StartTransactionRequest, |
14 | StartTransactionResponse, | |
89b7a234 JB |
15 | StopTransactionRequest, |
16 | StopTransactionResponse, | |
17 | } from '../types/ocpp/Transaction'; | |
18 | import { | |
19 | BroadcastChannelProcedureName, | |
20 | BroadcastChannelRequest, | |
6c8f5d90 JB |
21 | BroadcastChannelRequestPayload, |
22 | BroadcastChannelResponsePayload, | |
23 | MessageEvent, | |
89b7a234 | 24 | } from '../types/WorkerBroadcastChannel'; |
f27eb751 | 25 | import { ResponseStatus } from '../ui/web/src/types/UIProtocol'; |
6c8f5d90 | 26 | import logger from '../utils/Logger'; |
a9ed42b2 | 27 | import Utils from '../utils/Utils'; |
db2336d9 | 28 | import type ChargingStation from './ChargingStation'; |
1598b27c | 29 | import WorkerBroadcastChannel from './WorkerBroadcastChannel'; |
89b7a234 | 30 | |
4e3ff94d JB |
31 | const moduleName = 'ChargingStationWorkerBroadcastChannel'; |
32 | ||
a9ed42b2 JB |
33 | type CommandResponse = |
34 | | StartTransactionResponse | |
35 | | StopTransactionResponse | |
1984f194 | 36 | | AuthorizeResponse |
10db00b2 JB |
37 | | StatusNotificationResponse |
38 | | HeartbeatResponse; | |
89b7a234 | 39 | |
d273692c JB |
40 | type CommandHandler = ( |
41 | requestPayload?: BroadcastChannelRequestPayload | |
42 | ) => Promise<CommandResponse | void> | void; | |
43 | ||
1598b27c | 44 | export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { |
d273692c | 45 | private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>; |
9d73266c | 46 | |
89b7a234 JB |
47 | private readonly chargingStation: ChargingStation; |
48 | ||
49 | constructor(chargingStation: ChargingStation) { | |
1598b27c | 50 | super(); |
d273692c | 51 | this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([ |
9d73266c JB |
52 | [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()], |
53 | [ | |
54 | BroadcastChannelProcedureName.STOP_CHARGING_STATION, | |
d273692c | 55 | async () => this.chargingStation.stop(), |
9d73266c JB |
56 | ], |
57 | [ | |
58 | BroadcastChannelProcedureName.OPEN_CONNECTION, | |
59 | () => this.chargingStation.openWSConnection(), | |
60 | ], | |
61 | [ | |
62 | BroadcastChannelProcedureName.CLOSE_CONNECTION, | |
63 | () => this.chargingStation.closeWSConnection(), | |
64 | ], | |
623b39b5 JB |
65 | [ |
66 | BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, | |
67 | (requestPayload?: BroadcastChannelRequestPayload) => | |
68 | this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds), | |
69 | ], | |
70 | [ | |
71 | BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, | |
72 | (requestPayload?: BroadcastChannelRequestPayload) => | |
73 | this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds), | |
74 | ], | |
9d73266c JB |
75 | [ |
76 | BroadcastChannelProcedureName.START_TRANSACTION, | |
77 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
78 | this.chargingStation.ocppRequestService.requestHandler< | |
79 | StartTransactionRequest, | |
80 | StartTransactionResponse | |
1984f194 | 81 | >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload), |
9d73266c JB |
82 | ], |
83 | [ | |
84 | BroadcastChannelProcedureName.STOP_TRANSACTION, | |
85 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
86 | this.chargingStation.ocppRequestService.requestHandler< | |
87 | StopTransactionRequest, | |
88 | StartTransactionResponse | |
89 | >(this.chargingStation, RequestCommand.STOP_TRANSACTION, { | |
1984f194 | 90 | ...requestPayload, |
9d73266c JB |
91 | meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId( |
92 | requestPayload.transactionId, | |
93 | true | |
94 | ), | |
9d73266c JB |
95 | }), |
96 | ], | |
1984f194 JB |
97 | [ |
98 | BroadcastChannelProcedureName.AUTHORIZE, | |
99 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
100 | this.chargingStation.ocppRequestService.requestHandler< | |
101 | AuthorizeRequest, | |
102 | AuthorizeResponse | |
103 | >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload), | |
104 | ], | |
9d73266c JB |
105 | [ |
106 | BroadcastChannelProcedureName.STATUS_NOTIFICATION, | |
107 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
108 | this.chargingStation.ocppRequestService.requestHandler< | |
109 | StatusNotificationRequest, | |
110 | StatusNotificationResponse | |
1984f194 | 111 | >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload), |
9d73266c JB |
112 | ], |
113 | [ | |
114 | BroadcastChannelProcedureName.HEARTBEAT, | |
1984f194 JB |
115 | async (requestPayload?: BroadcastChannelRequestPayload) => |
116 | this.chargingStation.ocppRequestService.requestHandler< | |
9d73266c JB |
117 | HeartbeatRequest, |
118 | HeartbeatResponse | |
1984f194 | 119 | >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload), |
9d73266c JB |
120 | ], |
121 | ]); | |
89b7a234 | 122 | this.chargingStation = chargingStation; |
02a6943a | 123 | this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void; |
6c8f5d90 | 124 | this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void; |
89b7a234 JB |
125 | } |
126 | ||
02a6943a | 127 | private async requestHandler(messageEvent: MessageEvent): Promise<void> { |
5dea4c94 JB |
128 | const validatedMessageEvent = this.validateMessageEvent(messageEvent); |
129 | if (validatedMessageEvent === false) { | |
6c8f5d90 JB |
130 | return; |
131 | } | |
5dea4c94 JB |
132 | if (this.isResponse(validatedMessageEvent.data) === true) { |
133 | return; | |
134 | } | |
135 | const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest; | |
6c8f5d90 | 136 | |
2afb4d15 JB |
137 | if ( |
138 | requestPayload?.hashIds !== undefined && | |
139 | requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false | |
140 | ) { | |
141 | return; | |
142 | } | |
143 | if (requestPayload?.hashId !== undefined) { | |
144 | logger.error( | |
145 | `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead` | |
146 | ); | |
147 | return; | |
4eca248c | 148 | } |
6c8f5d90 | 149 | let responsePayload: BroadcastChannelResponsePayload; |
9d73266c | 150 | let commandResponse: CommandResponse | void; |
6c8f5d90 JB |
151 | try { |
152 | commandResponse = await this.commandHandler(command, requestPayload); | |
9d73266c | 153 | if (commandResponse === undefined || commandResponse === null) { |
10d244c0 | 154 | responsePayload = { |
51c83d6f | 155 | hashId: this.chargingStation.stationInfo.hashId, |
10d244c0 JB |
156 | status: ResponseStatus.SUCCESS, |
157 | }; | |
6c8f5d90 | 158 | } else { |
5e8e29f4 | 159 | const commandResponseStatus = this.commandResponseToResponseStatus( |
1984f194 JB |
160 | command, |
161 | commandResponse as CommandResponse | |
162 | ); | |
5e8e29f4 | 163 | if (commandResponseStatus === ResponseStatus.SUCCESS) { |
1984f194 JB |
164 | responsePayload = { |
165 | hashId: this.chargingStation.stationInfo.hashId, | |
5e8e29f4 | 166 | status: commandResponseStatus, |
1984f194 JB |
167 | }; |
168 | } else { | |
169 | responsePayload = { | |
170 | hashId: this.chargingStation.stationInfo.hashId, | |
5e8e29f4 | 171 | status: commandResponseStatus, |
1984f194 JB |
172 | command, |
173 | requestPayload, | |
174 | commandResponse: commandResponse as CommandResponse, | |
175 | }; | |
176 | } | |
6c8f5d90 JB |
177 | } |
178 | } catch (error) { | |
179 | logger.error( | |
180 | `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`, | |
181 | error | |
182 | ); | |
183 | responsePayload = { | |
51c83d6f | 184 | hashId: this.chargingStation.stationInfo.hashId, |
6c8f5d90 JB |
185 | status: ResponseStatus.FAILURE, |
186 | command, | |
187 | requestPayload, | |
9d73266c | 188 | commandResponse: commandResponse as CommandResponse, |
6c8f5d90 JB |
189 | errorMessage: (error as Error).message, |
190 | errorStack: (error as Error).stack, | |
a9ed42b2 | 191 | errorDetails: (error as OCPPError).details, |
6c8f5d90 | 192 | }; |
623b39b5 JB |
193 | } finally { |
194 | this.sendResponse([uuid, responsePayload]); | |
6c8f5d90 | 195 | } |
6c8f5d90 JB |
196 | } |
197 | ||
198 | private messageErrorHandler(messageEvent: MessageEvent): void { | |
199 | logger.error( | |
200 | `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`, | |
5e3cb728 | 201 | { messageEvent } |
6c8f5d90 JB |
202 | ); |
203 | } | |
204 | ||
205 | private async commandHandler( | |
206 | command: BroadcastChannelProcedureName, | |
207 | requestPayload: BroadcastChannelRequestPayload | |
9d73266c JB |
208 | ): Promise<CommandResponse | void> { |
209 | if (this.commandHandlers.has(command) === true) { | |
1984f194 | 210 | this.cleanRequestPayload(command, requestPayload); |
9d73266c | 211 | return this.commandHandlers.get(command)(requestPayload); |
6c8f5d90 | 212 | } |
9d73266c | 213 | throw new BaseError(`Unknown worker broadcast channel command: ${command}`); |
6c8f5d90 JB |
214 | } |
215 | ||
1984f194 JB |
216 | private cleanRequestPayload( |
217 | command: BroadcastChannelProcedureName, | |
218 | requestPayload: BroadcastChannelRequestPayload | |
219 | ): void { | |
220 | delete requestPayload.hashId; | |
221 | delete requestPayload.hashIds; | |
222 | [ | |
223 | BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, | |
224 | BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, | |
225 | ].includes(command) === false && delete requestPayload.connectorIds; | |
226 | } | |
227 | ||
10db00b2 JB |
228 | private commandResponseToResponseStatus( |
229 | command: BroadcastChannelProcedureName, | |
230 | commandResponse: CommandResponse | |
231 | ): ResponseStatus { | |
232 | switch (command) { | |
233 | case BroadcastChannelProcedureName.START_TRANSACTION: | |
234 | case BroadcastChannelProcedureName.STOP_TRANSACTION: | |
1984f194 | 235 | case BroadcastChannelProcedureName.AUTHORIZE: |
10db00b2 | 236 | if ( |
1984f194 JB |
237 | ( |
238 | commandResponse as | |
239 | | StartTransactionResponse | |
240 | | StopTransactionResponse | |
241 | | AuthorizeResponse | |
242 | )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED | |
10db00b2 JB |
243 | ) { |
244 | return ResponseStatus.SUCCESS; | |
245 | } | |
246 | return ResponseStatus.FAILURE; | |
247 | case BroadcastChannelProcedureName.STATUS_NOTIFICATION: | |
248 | if (Utils.isEmptyObject(commandResponse) === true) { | |
249 | return ResponseStatus.SUCCESS; | |
250 | } | |
251 | return ResponseStatus.FAILURE; | |
252 | case BroadcastChannelProcedureName.HEARTBEAT: | |
253 | if ('currentTime' in commandResponse) { | |
254 | return ResponseStatus.SUCCESS; | |
255 | } | |
256 | return ResponseStatus.FAILURE; | |
257 | default: | |
258 | return ResponseStatus.FAILURE; | |
89b7a234 JB |
259 | } |
260 | } | |
261 | } |