Commit | Line | Data |
---|---|---|
6c8f5d90 | 1 | import BaseError from '../exception/BaseError'; |
a9ed42b2 | 2 | import type OCPPError from '../exception/OCPPError'; |
d3195f0a | 3 | import { StandardParametersKey } from '../types/ocpp/Configuration'; |
10db00b2 | 4 | import { |
8bfbc743 | 5 | type BootNotificationRequest, |
91a7d3ea | 6 | type DataTransferRequest, |
c9a4f9ea JB |
7 | type DiagnosticsStatusNotificationRequest, |
8 | type FirmwareStatusNotificationRequest, | |
8bfbc743 JB |
9 | type HeartbeatRequest, |
10 | type MeterValuesRequest, | |
10db00b2 JB |
11 | RequestCommand, |
12 | type StatusNotificationRequest, | |
13 | } from '../types/ocpp/Requests'; | |
8bfbc743 JB |
14 | import { |
15 | type BootNotificationResponse, | |
91a7d3ea JB |
16 | type DataTransferResponse, |
17 | DataTransferStatus, | |
c9a4f9ea JB |
18 | type DiagnosticsStatusNotificationResponse, |
19 | type FirmwareStatusNotificationResponse, | |
8bfbc743 JB |
20 | type HeartbeatResponse, |
21 | type MeterValuesResponse, | |
d270cc87 | 22 | RegistrationStatusEnumType, |
8bfbc743 | 23 | type StatusNotificationResponse, |
d3195f0a | 24 | } from '../types/ocpp/Responses'; |
89b7a234 | 25 | import { |
6c8f5d90 | 26 | AuthorizationStatus, |
8bfbc743 JB |
27 | type AuthorizeRequest, |
28 | type AuthorizeResponse, | |
29 | type StartTransactionRequest, | |
30 | type StartTransactionResponse, | |
31 | type StopTransactionRequest, | |
32 | type StopTransactionResponse, | |
89b7a234 | 33 | } from '../types/ocpp/Transaction'; |
c91bd158 | 34 | import { ResponseStatus } from '../types/UIProtocol'; |
89b7a234 JB |
35 | import { |
36 | BroadcastChannelProcedureName, | |
8bfbc743 JB |
37 | type BroadcastChannelRequest, |
38 | type BroadcastChannelRequestPayload, | |
39 | type BroadcastChannelResponsePayload, | |
40 | type MessageEvent, | |
89b7a234 | 41 | } from '../types/WorkerBroadcastChannel'; |
d3195f0a | 42 | import Constants from '../utils/Constants'; |
6c8f5d90 | 43 | import logger from '../utils/Logger'; |
a9ed42b2 | 44 | import Utils from '../utils/Utils'; |
db2336d9 | 45 | import type ChargingStation from './ChargingStation'; |
d3195f0a JB |
46 | import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils'; |
47 | import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils'; | |
1598b27c | 48 | import WorkerBroadcastChannel from './WorkerBroadcastChannel'; |
89b7a234 | 49 | |
4e3ff94d JB |
50 | const moduleName = 'ChargingStationWorkerBroadcastChannel'; |
51 | ||
a9ed42b2 JB |
52 | type CommandResponse = |
53 | | StartTransactionResponse | |
54 | | StopTransactionResponse | |
1984f194 | 55 | | AuthorizeResponse |
8bfbc743 | 56 | | BootNotificationResponse |
10db00b2 | 57 | | StatusNotificationResponse |
d3195f0a | 58 | | HeartbeatResponse |
91a7d3ea | 59 | | MeterValuesResponse |
c9a4f9ea JB |
60 | | DataTransferResponse |
61 | | DiagnosticsStatusNotificationResponse | |
62 | | FirmwareStatusNotificationResponse; | |
89b7a234 | 63 | |
d273692c JB |
64 | type CommandHandler = ( |
65 | requestPayload?: BroadcastChannelRequestPayload | |
66 | ) => Promise<CommandResponse | void> | void; | |
67 | ||
1598b27c | 68 | export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { |
d273692c | 69 | private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>; |
89b7a234 JB |
70 | private readonly chargingStation: ChargingStation; |
71 | ||
72 | constructor(chargingStation: ChargingStation) { | |
1598b27c | 73 | super(); |
d273692c | 74 | this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([ |
9d73266c JB |
75 | [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()], |
76 | [ | |
77 | BroadcastChannelProcedureName.STOP_CHARGING_STATION, | |
d273692c | 78 | async () => this.chargingStation.stop(), |
9d73266c JB |
79 | ], |
80 | [ | |
81 | BroadcastChannelProcedureName.OPEN_CONNECTION, | |
82 | () => this.chargingStation.openWSConnection(), | |
83 | ], | |
84 | [ | |
85 | BroadcastChannelProcedureName.CLOSE_CONNECTION, | |
86 | () => this.chargingStation.closeWSConnection(), | |
87 | ], | |
623b39b5 JB |
88 | [ |
89 | BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, | |
90 | (requestPayload?: BroadcastChannelRequestPayload) => | |
91 | this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds), | |
92 | ], | |
93 | [ | |
94 | BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, | |
95 | (requestPayload?: BroadcastChannelRequestPayload) => | |
96 | this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds), | |
97 | ], | |
9d73266c JB |
98 | [ |
99 | BroadcastChannelProcedureName.START_TRANSACTION, | |
100 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
101 | this.chargingStation.ocppRequestService.requestHandler< | |
102 | StartTransactionRequest, | |
103 | StartTransactionResponse | |
1984f194 | 104 | >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload), |
9d73266c JB |
105 | ], |
106 | [ | |
107 | BroadcastChannelProcedureName.STOP_TRANSACTION, | |
108 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
109 | this.chargingStation.ocppRequestService.requestHandler< | |
110 | StopTransactionRequest, | |
111 | StartTransactionResponse | |
112 | >(this.chargingStation, RequestCommand.STOP_TRANSACTION, { | |
2212319a JB |
113 | meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId( |
114 | requestPayload.transactionId, | |
115 | true | |
116 | ), | |
8bfbc743 | 117 | ...requestPayload, |
9d73266c JB |
118 | }), |
119 | ], | |
1984f194 JB |
120 | [ |
121 | BroadcastChannelProcedureName.AUTHORIZE, | |
122 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
123 | this.chargingStation.ocppRequestService.requestHandler< | |
124 | AuthorizeRequest, | |
125 | AuthorizeResponse | |
126 | >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload), | |
127 | ], | |
8bfbc743 JB |
128 | [ |
129 | BroadcastChannelProcedureName.BOOT_NOTIFICATION, | |
130 | async (requestPayload?: BroadcastChannelRequestPayload) => { | |
131 | this.chargingStation.bootNotificationResponse = | |
132 | await this.chargingStation.ocppRequestService.requestHandler< | |
133 | BootNotificationRequest, | |
134 | BootNotificationResponse | |
135 | >( | |
136 | this.chargingStation, | |
137 | RequestCommand.BOOT_NOTIFICATION, | |
138 | { | |
139 | ...this.chargingStation.bootNotificationRequest, | |
140 | ...requestPayload, | |
141 | }, | |
142 | { | |
143 | skipBufferingOnError: true, | |
144 | } | |
145 | ); | |
146 | return this.chargingStation.bootNotificationResponse; | |
147 | }, | |
148 | ], | |
9d73266c JB |
149 | [ |
150 | BroadcastChannelProcedureName.STATUS_NOTIFICATION, | |
151 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
152 | this.chargingStation.ocppRequestService.requestHandler< | |
153 | StatusNotificationRequest, | |
154 | StatusNotificationResponse | |
1984f194 | 155 | >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload), |
9d73266c JB |
156 | ], |
157 | [ | |
158 | BroadcastChannelProcedureName.HEARTBEAT, | |
1984f194 JB |
159 | async (requestPayload?: BroadcastChannelRequestPayload) => |
160 | this.chargingStation.ocppRequestService.requestHandler< | |
9d73266c JB |
161 | HeartbeatRequest, |
162 | HeartbeatResponse | |
1984f194 | 163 | >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload), |
9d73266c | 164 | ], |
d3195f0a JB |
165 | [ |
166 | BroadcastChannelProcedureName.METER_VALUES, | |
167 | async (requestPayload?: BroadcastChannelRequestPayload) => { | |
168 | const configuredMeterValueSampleInterval = | |
169 | ChargingStationConfigurationUtils.getConfigurationKey( | |
170 | chargingStation, | |
171 | StandardParametersKey.MeterValueSampleInterval | |
172 | ); | |
173 | return this.chargingStation.ocppRequestService.requestHandler< | |
174 | MeterValuesRequest, | |
175 | MeterValuesResponse | |
176 | >(this.chargingStation, RequestCommand.METER_VALUES, { | |
8bfbc743 | 177 | meterValue: [ |
d3195f0a JB |
178 | OCPP16ServiceUtils.buildMeterValue( |
179 | this.chargingStation, | |
180 | requestPayload.connectorId, | |
181 | this.chargingStation.getConnectorStatus(requestPayload.connectorId)?.transactionId, | |
182 | configuredMeterValueSampleInterval | |
183 | ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000 | |
184 | : Constants.DEFAULT_METER_VALUES_INTERVAL | |
185 | ), | |
186 | ], | |
8bfbc743 | 187 | ...requestPayload, |
d3195f0a JB |
188 | }); |
189 | }, | |
190 | ], | |
91a7d3ea JB |
191 | [ |
192 | BroadcastChannelProcedureName.DATA_TRANSFER, | |
193 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
194 | this.chargingStation.ocppRequestService.requestHandler< | |
195 | DataTransferRequest, | |
196 | DataTransferResponse | |
197 | >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload), | |
198 | ], | |
c9a4f9ea JB |
199 | [ |
200 | BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION, | |
201 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
202 | this.chargingStation.ocppRequestService.requestHandler< | |
203 | DiagnosticsStatusNotificationRequest, | |
204 | DiagnosticsStatusNotificationResponse | |
205 | >(this.chargingStation, RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, requestPayload), | |
206 | ], | |
207 | [ | |
208 | BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION, | |
209 | async (requestPayload?: BroadcastChannelRequestPayload) => | |
210 | this.chargingStation.ocppRequestService.requestHandler< | |
211 | FirmwareStatusNotificationRequest, | |
212 | FirmwareStatusNotificationResponse | |
213 | >(this.chargingStation, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, requestPayload), | |
214 | ], | |
9d73266c | 215 | ]); |
89b7a234 | 216 | this.chargingStation = chargingStation; |
02a6943a | 217 | this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void; |
6c8f5d90 | 218 | this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void; |
89b7a234 JB |
219 | } |
220 | ||
02a6943a | 221 | private async requestHandler(messageEvent: MessageEvent): Promise<void> { |
5dea4c94 JB |
222 | const validatedMessageEvent = this.validateMessageEvent(messageEvent); |
223 | if (validatedMessageEvent === false) { | |
6c8f5d90 JB |
224 | return; |
225 | } | |
5dea4c94 JB |
226 | if (this.isResponse(validatedMessageEvent.data) === true) { |
227 | return; | |
228 | } | |
229 | const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest; | |
2afb4d15 JB |
230 | if ( |
231 | requestPayload?.hashIds !== undefined && | |
232 | requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false | |
233 | ) { | |
234 | return; | |
235 | } | |
236 | if (requestPayload?.hashId !== undefined) { | |
237 | logger.error( | |
edd13439 | 238 | `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead` |
2afb4d15 JB |
239 | ); |
240 | return; | |
4eca248c | 241 | } |
6c8f5d90 | 242 | let responsePayload: BroadcastChannelResponsePayload; |
9d73266c | 243 | let commandResponse: CommandResponse | void; |
6c8f5d90 JB |
244 | try { |
245 | commandResponse = await this.commandHandler(command, requestPayload); | |
c9a4f9ea JB |
246 | if ( |
247 | commandResponse === undefined || | |
248 | commandResponse === null || | |
249 | Utils.isEmptyObject(commandResponse as CommandResponse) | |
250 | ) { | |
10d244c0 | 251 | responsePayload = { |
51c83d6f | 252 | hashId: this.chargingStation.stationInfo.hashId, |
10d244c0 JB |
253 | status: ResponseStatus.SUCCESS, |
254 | }; | |
6c8f5d90 | 255 | } else { |
d3195f0a | 256 | responsePayload = this.commandResponseToResponsePayload( |
1984f194 | 257 | command, |
d3195f0a | 258 | requestPayload, |
1984f194 JB |
259 | commandResponse as CommandResponse |
260 | ); | |
6c8f5d90 JB |
261 | } |
262 | } catch (error) { | |
263 | logger.error( | |
264 | `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`, | |
265 | error | |
266 | ); | |
267 | responsePayload = { | |
51c83d6f | 268 | hashId: this.chargingStation.stationInfo.hashId, |
6c8f5d90 JB |
269 | status: ResponseStatus.FAILURE, |
270 | command, | |
271 | requestPayload, | |
9d73266c | 272 | commandResponse: commandResponse as CommandResponse, |
6c8f5d90 JB |
273 | errorMessage: (error as Error).message, |
274 | errorStack: (error as Error).stack, | |
a9ed42b2 | 275 | errorDetails: (error as OCPPError).details, |
6c8f5d90 | 276 | }; |
623b39b5 JB |
277 | } finally { |
278 | this.sendResponse([uuid, responsePayload]); | |
6c8f5d90 | 279 | } |
6c8f5d90 JB |
280 | } |
281 | ||
282 | private messageErrorHandler(messageEvent: MessageEvent): void { | |
283 | logger.error( | |
284 | `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`, | |
5083d31a | 285 | messageEvent |
6c8f5d90 JB |
286 | ); |
287 | } | |
288 | ||
289 | private async commandHandler( | |
290 | command: BroadcastChannelProcedureName, | |
291 | requestPayload: BroadcastChannelRequestPayload | |
9d73266c JB |
292 | ): Promise<CommandResponse | void> { |
293 | if (this.commandHandlers.has(command) === true) { | |
1984f194 | 294 | this.cleanRequestPayload(command, requestPayload); |
9d73266c | 295 | return this.commandHandlers.get(command)(requestPayload); |
6c8f5d90 | 296 | } |
9d73266c | 297 | throw new BaseError(`Unknown worker broadcast channel command: ${command}`); |
6c8f5d90 JB |
298 | } |
299 | ||
1984f194 JB |
300 | private cleanRequestPayload( |
301 | command: BroadcastChannelProcedureName, | |
302 | requestPayload: BroadcastChannelRequestPayload | |
303 | ): void { | |
304 | delete requestPayload.hashId; | |
305 | delete requestPayload.hashIds; | |
306 | [ | |
307 | BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, | |
308 | BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, | |
8f879946 | 309 | ].includes(command) === false && delete requestPayload.connectorIds; |
1984f194 JB |
310 | } |
311 | ||
d3195f0a JB |
312 | private commandResponseToResponsePayload( |
313 | command: BroadcastChannelProcedureName, | |
314 | requestPayload: BroadcastChannelRequestPayload, | |
315 | commandResponse: CommandResponse | |
316 | ): BroadcastChannelResponsePayload { | |
cfa257f5 JB |
317 | const responseStatus = this.commandResponseToResponseStatus(command, commandResponse); |
318 | if (responseStatus === ResponseStatus.SUCCESS) { | |
d3195f0a JB |
319 | return { |
320 | hashId: this.chargingStation.stationInfo.hashId, | |
cfa257f5 | 321 | status: responseStatus, |
d3195f0a JB |
322 | }; |
323 | } | |
324 | return { | |
325 | hashId: this.chargingStation.stationInfo.hashId, | |
cfa257f5 | 326 | status: responseStatus, |
d3195f0a JB |
327 | command, |
328 | requestPayload, | |
329 | commandResponse, | |
330 | }; | |
331 | } | |
332 | ||
cfa257f5 | 333 | private commandResponseToResponseStatus( |
10db00b2 JB |
334 | command: BroadcastChannelProcedureName, |
335 | commandResponse: CommandResponse | |
336 | ): ResponseStatus { | |
337 | switch (command) { | |
338 | case BroadcastChannelProcedureName.START_TRANSACTION: | |
339 | case BroadcastChannelProcedureName.STOP_TRANSACTION: | |
1984f194 | 340 | case BroadcastChannelProcedureName.AUTHORIZE: |
10db00b2 | 341 | if ( |
1984f194 JB |
342 | ( |
343 | commandResponse as | |
344 | | StartTransactionResponse | |
345 | | StopTransactionResponse | |
346 | | AuthorizeResponse | |
347 | )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED | |
10db00b2 JB |
348 | ) { |
349 | return ResponseStatus.SUCCESS; | |
350 | } | |
351 | return ResponseStatus.FAILURE; | |
8bfbc743 | 352 | case BroadcastChannelProcedureName.BOOT_NOTIFICATION: |
d270cc87 | 353 | if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) { |
8bfbc743 JB |
354 | return ResponseStatus.SUCCESS; |
355 | } | |
356 | return ResponseStatus.FAILURE; | |
91a7d3ea JB |
357 | case BroadcastChannelProcedureName.DATA_TRANSFER: |
358 | if (commandResponse?.status === DataTransferStatus.ACCEPTED) { | |
359 | return ResponseStatus.SUCCESS; | |
360 | } | |
361 | return ResponseStatus.FAILURE; | |
10db00b2 | 362 | case BroadcastChannelProcedureName.STATUS_NOTIFICATION: |
d3195f0a | 363 | case BroadcastChannelProcedureName.METER_VALUES: |
10db00b2 JB |
364 | if (Utils.isEmptyObject(commandResponse) === true) { |
365 | return ResponseStatus.SUCCESS; | |
366 | } | |
367 | return ResponseStatus.FAILURE; | |
368 | case BroadcastChannelProcedureName.HEARTBEAT: | |
369 | if ('currentTime' in commandResponse) { | |
370 | return ResponseStatus.SUCCESS; | |
371 | } | |
372 | return ResponseStatus.FAILURE; | |
373 | default: | |
374 | return ResponseStatus.FAILURE; | |
89b7a234 JB |
375 | } |
376 | } | |
377 | } |