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