]>
Commit | Line | Data |
---|---|---|
a19b897d | 1 | // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. |
c8eeb62b | 2 | |
24d15716 | 3 | import type { ValidateFunction } from 'ajv' |
0749233f | 4 | |
66a7748d | 5 | import { secondsToMilliseconds } from 'date-fns' |
844e496b | 6 | |
04b1261c | 7 | import { |
f2d5e3d9 | 8 | addConfigurationKey, |
4c3f6c20 | 9 | type ChargingStation, |
f2d5e3d9 | 10 | getConfigurationKey, |
90aceaf6 | 11 | hasReservationExpired, |
d1f5bfd8 | 12 | resetConnectorStatus, |
66a7748d JB |
13 | } from '../../../charging-station/index.js' |
14 | import { OCPPError } from '../../../exception/index.js' | |
ef6fa3fb | 15 | import { |
268a74bb | 16 | type ChangeConfigurationResponse, |
50539084 | 17 | ChargingStationEvents, |
268a74bb JB |
18 | ErrorType, |
19 | type GenericResponse, | |
20 | type GetConfigurationResponse, | |
21 | type GetDiagnosticsResponse, | |
268a74bb | 22 | type JsonType, |
8114d10e | 23 | OCPP16AuthorizationStatus, |
27782dbc JB |
24 | type OCPP16AuthorizeRequest, |
25 | type OCPP16AuthorizeResponse, | |
268a74bb | 26 | type OCPP16BootNotificationResponse, |
366f75f6 | 27 | type OCPP16ChangeAvailabilityResponse, |
268a74bb | 28 | OCPP16ChargePointStatus, |
41f3983a | 29 | type OCPP16ClearChargingProfileResponse, |
268a74bb JB |
30 | type OCPP16DataTransferResponse, |
31 | type OCPP16DiagnosticsStatusNotificationResponse, | |
32 | type OCPP16FirmwareStatusNotificationResponse, | |
41189456 | 33 | type OCPP16GetCompositeScheduleResponse, |
268a74bb JB |
34 | type OCPP16HeartbeatResponse, |
35 | OCPP16IncomingRequestCommand, | |
36 | type OCPP16MeterValuesRequest, | |
37 | type OCPP16MeterValuesResponse, | |
38 | OCPP16RequestCommand, | |
66dd3447 | 39 | type OCPP16ReserveNowResponse, |
268a74bb | 40 | OCPP16StandardParametersKey, |
27782dbc JB |
41 | type OCPP16StartTransactionRequest, |
42 | type OCPP16StartTransactionResponse, | |
268a74bb | 43 | type OCPP16StatusNotificationResponse, |
27782dbc JB |
44 | type OCPP16StopTransactionRequest, |
45 | type OCPP16StopTransactionResponse, | |
268a74bb JB |
46 | type OCPP16TriggerMessageResponse, |
47 | type OCPP16UpdateFirmwareResponse, | |
48 | OCPPVersion, | |
02887891 | 49 | RegistrationStatusEnumType, |
d984c13f | 50 | ReservationTerminationReason, |
02887891 | 51 | type ResponseHandler, |
268a74bb | 52 | type SetChargingProfileResponse, |
d1f5bfd8 | 53 | type UnlockConnectorResponse, |
66a7748d | 54 | } from '../../../types/index.js' |
bcf95df1 | 55 | import { Constants, convertToInt, isAsyncFunction, logger } from '../../../utils/index.js' |
66a7748d | 56 | import { OCPPResponseService } from '../OCPPResponseService.js' |
4c3f6c20 | 57 | import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js' |
c0560973 | 58 | |
66a7748d | 59 | const moduleName = 'OCPP16ResponseService' |
909dcf2d | 60 | |
268a74bb | 61 | export class OCPP16ResponseService extends OCPPResponseService { |
d5490a13 | 62 | public incomingRequestResponsePayloadValidateFunctions: Map< |
d1f5bfd8 JB |
63 | OCPP16IncomingRequestCommand, |
64 | ValidateFunction<JsonType> | |
66a7748d | 65 | > |
b3fc3ff5 | 66 | |
c4a89082 JB |
67 | protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>> |
68 | private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler> | |
69 | ||
66a7748d | 70 | public constructor () { |
5199f9fd JB |
71 | // if (new.target.name === moduleName) { |
72 | // throw new TypeError(`Cannot construct ${new.target.name} instances directly`) | |
b768993d | 73 | // } |
66a7748d | 74 | super(OCPPVersion.VERSION_16) |
58144adb | 75 | this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([ |
0749233f | 76 | [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler], |
a37fc6dc JB |
77 | [ |
78 | OCPP16RequestCommand.BOOT_NOTIFICATION, | |
d1f5bfd8 | 79 | this.handleResponseBootNotification.bind(this) as ResponseHandler, |
a37fc6dc | 80 | ], |
0749233f JB |
81 | [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler], |
82 | [ | |
83 | OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, | |
84 | this.emptyResponseHandler.bind(this) as ResponseHandler, | |
85 | ], | |
86 | [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler], | |
65b5177e | 87 | [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler], |
0749233f | 88 | [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler], |
a37fc6dc JB |
89 | [ |
90 | OCPP16RequestCommand.START_TRANSACTION, | |
d1f5bfd8 | 91 | this.handleResponseStartTransaction.bind(this) as ResponseHandler, |
a37fc6dc | 92 | ], |
a37fc6dc JB |
93 | [ |
94 | OCPP16RequestCommand.STATUS_NOTIFICATION, | |
d1f5bfd8 | 95 | this.emptyResponseHandler.bind(this) as ResponseHandler, |
a37fc6dc | 96 | ], |
a37fc6dc | 97 | [ |
0749233f JB |
98 | OCPP16RequestCommand.STOP_TRANSACTION, |
99 | this.handleResponseStopTransaction.bind(this) as ResponseHandler, | |
a37fc6dc | 100 | ], |
66a7748d | 101 | ]) |
d5490a13 | 102 | this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([ |
b52c969d | 103 | [ |
0749233f | 104 | OCPP16RequestCommand.AUTHORIZE, |
d712a9a3 | 105 | this.ajv.compile( |
0749233f JB |
106 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>( |
107 | 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json', | |
d712a9a3 JB |
108 | moduleName, |
109 | 'constructor' | |
110 | ) | |
111 | ), | |
b52c969d JB |
112 | ], |
113 | [ | |
0749233f | 114 | OCPP16RequestCommand.BOOT_NOTIFICATION, |
d712a9a3 | 115 | this.ajv.compile( |
0749233f JB |
116 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>( |
117 | 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json', | |
d712a9a3 JB |
118 | moduleName, |
119 | 'constructor' | |
120 | ) | |
121 | ), | |
b52c969d JB |
122 | ], |
123 | [ | |
0749233f | 124 | OCPP16RequestCommand.DATA_TRANSFER, |
d712a9a3 | 125 | this.ajv.compile( |
0749233f JB |
126 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>( |
127 | 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json', | |
d712a9a3 JB |
128 | moduleName, |
129 | 'constructor' | |
130 | ) | |
131 | ), | |
b52c969d JB |
132 | ], |
133 | [ | |
0749233f | 134 | OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, |
d712a9a3 | 135 | this.ajv.compile( |
0749233f JB |
136 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>( |
137 | 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json', | |
d712a9a3 JB |
138 | moduleName, |
139 | 'constructor' | |
140 | ) | |
141 | ), | |
b52c969d JB |
142 | ], |
143 | [ | |
0749233f | 144 | OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, |
d712a9a3 | 145 | this.ajv.compile( |
0749233f JB |
146 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>( |
147 | 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json', | |
d712a9a3 JB |
148 | moduleName, |
149 | 'constructor' | |
150 | ) | |
151 | ), | |
b52c969d JB |
152 | ], |
153 | [ | |
0749233f | 154 | OCPP16RequestCommand.HEARTBEAT, |
d712a9a3 | 155 | this.ajv.compile( |
0749233f JB |
156 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>( |
157 | 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json', | |
d712a9a3 JB |
158 | moduleName, |
159 | 'constructor' | |
160 | ) | |
161 | ), | |
b52c969d JB |
162 | ], |
163 | [ | |
164 | OCPP16RequestCommand.METER_VALUES, | |
d712a9a3 JB |
165 | this.ajv.compile( |
166 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>( | |
167 | 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json', | |
168 | moduleName, | |
169 | 'constructor' | |
170 | ) | |
171 | ), | |
b52c969d JB |
172 | ], |
173 | [ | |
0749233f | 174 | OCPP16RequestCommand.START_TRANSACTION, |
d712a9a3 | 175 | this.ajv.compile( |
0749233f JB |
176 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>( |
177 | 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json', | |
d712a9a3 JB |
178 | moduleName, |
179 | 'constructor' | |
180 | ) | |
181 | ), | |
b52c969d | 182 | ], |
91a7d3ea | 183 | [ |
0749233f | 184 | OCPP16RequestCommand.STATUS_NOTIFICATION, |
d712a9a3 | 185 | this.ajv.compile( |
0749233f JB |
186 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>( |
187 | 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json', | |
d712a9a3 JB |
188 | moduleName, |
189 | 'constructor' | |
190 | ) | |
191 | ), | |
91a7d3ea | 192 | ], |
c9a4f9ea | 193 | [ |
0749233f | 194 | OCPP16RequestCommand.STOP_TRANSACTION, |
d712a9a3 | 195 | this.ajv.compile( |
0749233f JB |
196 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>( |
197 | 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json', | |
d712a9a3 JB |
198 | moduleName, |
199 | 'constructor' | |
200 | ) | |
201 | ), | |
d1f5bfd8 | 202 | ], |
66a7748d | 203 | ]) |
d5490a13 | 204 | this.incomingRequestResponsePayloadValidateFunctions = new Map< |
d1f5bfd8 JB |
205 | OCPP16IncomingRequestCommand, |
206 | ValidateFunction<JsonType> | |
24d15716 | 207 | >([ |
02887891 | 208 | [ |
0749233f | 209 | OCPP16IncomingRequestCommand.CANCEL_RESERVATION, |
d712a9a3 JB |
210 | this.ajvIncomingRequest.compile( |
211 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( | |
0749233f | 212 | 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json', |
d712a9a3 JB |
213 | moduleName, |
214 | 'constructor' | |
215 | ) | |
216 | ), | |
02887891 JB |
217 | ], |
218 | [ | |
0749233f | 219 | OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, |
d712a9a3 | 220 | this.ajvIncomingRequest.compile( |
0749233f JB |
221 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>( |
222 | 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json', | |
d712a9a3 JB |
223 | moduleName, |
224 | 'constructor' | |
225 | ) | |
226 | ), | |
02887891 JB |
227 | ], |
228 | [ | |
0749233f | 229 | OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, |
d712a9a3 | 230 | this.ajvIncomingRequest.compile( |
0749233f JB |
231 | OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>( |
232 | 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json', | |
d712a9a3 JB |
233 | moduleName, |
234 | 'constructor' | |
235 | ) | |
236 | ), | |
02887891 JB |
237 | ], |
238 | [ | |
0749233f | 239 | OCPP16IncomingRequestCommand.CLEAR_CACHE, |
d712a9a3 | 240 | this.ajvIncomingRequest.compile( |
0749233f JB |
241 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
242 | 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json', | |
d712a9a3 JB |
243 | moduleName, |
244 | 'constructor' | |
245 | ) | |
246 | ), | |
02887891 JB |
247 | ], |
248 | [ | |
0749233f | 249 | OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, |
d712a9a3 | 250 | this.ajvIncomingRequest.compile( |
0749233f JB |
251 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>( |
252 | 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json', | |
d712a9a3 JB |
253 | moduleName, |
254 | 'constructor' | |
255 | ) | |
256 | ), | |
02887891 JB |
257 | ], |
258 | [ | |
0749233f | 259 | OCPP16IncomingRequestCommand.DATA_TRANSFER, |
d712a9a3 | 260 | this.ajvIncomingRequest.compile( |
0749233f JB |
261 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>( |
262 | 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json', | |
d712a9a3 JB |
263 | moduleName, |
264 | 'constructor' | |
265 | ) | |
266 | ), | |
02887891 | 267 | ], |
41189456 JB |
268 | [ |
269 | OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, | |
d712a9a3 JB |
270 | this.ajvIncomingRequest.compile( |
271 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>( | |
272 | 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json', | |
273 | moduleName, | |
274 | 'constructor' | |
275 | ) | |
276 | ), | |
41189456 | 277 | ], |
02887891 | 278 | [ |
0749233f | 279 | OCPP16IncomingRequestCommand.GET_CONFIGURATION, |
d712a9a3 | 280 | this.ajvIncomingRequest.compile( |
0749233f JB |
281 | OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>( |
282 | 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json', | |
d712a9a3 JB |
283 | moduleName, |
284 | 'constructor' | |
285 | ) | |
286 | ), | |
02887891 JB |
287 | ], |
288 | [ | |
0749233f | 289 | OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, |
d712a9a3 | 290 | this.ajvIncomingRequest.compile( |
0749233f JB |
291 | OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>( |
292 | 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json', | |
d712a9a3 JB |
293 | moduleName, |
294 | 'constructor' | |
295 | ) | |
296 | ), | |
02887891 JB |
297 | ], |
298 | [ | |
299 | OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, | |
d712a9a3 JB |
300 | this.ajvIncomingRequest.compile( |
301 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( | |
302 | 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json', | |
303 | moduleName, | |
304 | 'constructor' | |
305 | ) | |
306 | ), | |
02887891 JB |
307 | ], |
308 | [ | |
309 | OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, | |
d712a9a3 JB |
310 | this.ajvIncomingRequest.compile( |
311 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( | |
312 | 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json', | |
313 | moduleName, | |
314 | 'constructor' | |
315 | ) | |
316 | ), | |
02887891 JB |
317 | ], |
318 | [ | |
0749233f | 319 | OCPP16IncomingRequestCommand.RESERVE_NOW, |
d712a9a3 | 320 | this.ajvIncomingRequest.compile( |
0749233f JB |
321 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>( |
322 | 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json', | |
d712a9a3 JB |
323 | moduleName, |
324 | 'constructor' | |
325 | ) | |
326 | ), | |
02887891 JB |
327 | ], |
328 | [ | |
0749233f | 329 | OCPP16IncomingRequestCommand.RESET, |
d712a9a3 | 330 | this.ajvIncomingRequest.compile( |
0749233f JB |
331 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
332 | 'assets/json-schemas/ocpp/1.6/ResetResponse.json', | |
d712a9a3 JB |
333 | moduleName, |
334 | 'constructor' | |
335 | ) | |
336 | ), | |
02887891 JB |
337 | ], |
338 | [ | |
0749233f | 339 | OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, |
d712a9a3 | 340 | this.ajvIncomingRequest.compile( |
0749233f JB |
341 | OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>( |
342 | 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json', | |
d712a9a3 JB |
343 | moduleName, |
344 | 'constructor' | |
345 | ) | |
346 | ), | |
02887891 JB |
347 | ], |
348 | [ | |
0749233f | 349 | OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, |
d712a9a3 | 350 | this.ajvIncomingRequest.compile( |
0749233f JB |
351 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>( |
352 | 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json', | |
d712a9a3 JB |
353 | moduleName, |
354 | 'constructor' | |
355 | ) | |
356 | ), | |
02887891 | 357 | ], |
28fe900f | 358 | [ |
0749233f | 359 | OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, |
d712a9a3 | 360 | this.ajvIncomingRequest.compile( |
0749233f JB |
361 | OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>( |
362 | 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json', | |
d712a9a3 JB |
363 | moduleName, |
364 | 'constructor' | |
365 | ) | |
366 | ), | |
28fe900f JB |
367 | ], |
368 | [ | |
0749233f | 369 | OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, |
d712a9a3 | 370 | this.ajvIncomingRequest.compile( |
0749233f JB |
371 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>( |
372 | 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json', | |
d712a9a3 JB |
373 | moduleName, |
374 | 'constructor' | |
375 | ) | |
376 | ), | |
d1f5bfd8 | 377 | ], |
66a7748d | 378 | ]) |
ba9a56a6 | 379 | this.validatePayload = this.validatePayload.bind(this) |
58144adb JB |
380 | } |
381 | ||
c4a89082 JB |
382 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters |
383 | public async responseHandler<ReqType extends JsonType, ResType extends JsonType>( | |
384 | chargingStation: ChargingStation, | |
385 | commandName: OCPP16RequestCommand, | |
386 | payload: ResType, | |
387 | requestPayload: ReqType | |
388 | ): Promise<void> { | |
b72d1749 JB |
389 | if ( |
390 | chargingStation.inAcceptedState() || | |
391 | ((chargingStation.inUnknownState() || chargingStation.inPendingState()) && | |
392 | commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) || | |
393 | (chargingStation.stationInfo?.ocppStrictCompliance === false && | |
394 | (chargingStation.inUnknownState() || chargingStation.inPendingState())) | |
395 | ) { | |
c4a89082 JB |
396 | if ( |
397 | this.responseHandlers.has(commandName) && | |
398 | OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName) | |
399 | ) { | |
400 | try { | |
401 | this.validatePayload(chargingStation, commandName, payload) | |
402 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
403 | const responseHandler = this.responseHandlers.get(commandName)! | |
404 | if (isAsyncFunction(responseHandler)) { | |
405 | await responseHandler(chargingStation, payload, requestPayload) | |
406 | } else { | |
407 | ;( | |
408 | responseHandler as ( | |
409 | chargingStation: ChargingStation, | |
410 | payload: JsonType, | |
411 | requestPayload?: JsonType | |
412 | ) => void | |
413 | )(chargingStation, payload, requestPayload) | |
414 | } | |
415 | } catch (error) { | |
416 | logger.error( | |
417 | `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`, | |
418 | error | |
419 | ) | |
420 | throw error | |
421 | } | |
422 | } else { | |
423 | // Throw exception | |
424 | throw new OCPPError( | |
425 | ErrorType.NOT_IMPLEMENTED, | |
426 | `${commandName} is not implemented to handle response PDU ${JSON.stringify( | |
427 | payload, | |
428 | undefined, | |
429 | 2 | |
430 | )}`, | |
431 | commandName, | |
432 | payload | |
433 | ) | |
434 | } | |
435 | } else { | |
436 | throw new OCPPError( | |
437 | ErrorType.SECURITY_ERROR, | |
438 | `${commandName} cannot be issued to handle response PDU ${JSON.stringify( | |
439 | payload, | |
440 | undefined, | |
441 | 2 | |
442 | )} while the charging station is not registered on the central server`, | |
443 | commandName, | |
444 | payload | |
445 | ) | |
446 | } | |
447 | } | |
448 | ||
66a7748d | 449 | private handleResponseAuthorize ( |
08f130a0 | 450 | chargingStation: ChargingStation, |
e7aeea18 | 451 | payload: OCPP16AuthorizeResponse, |
66a7748d | 452 | requestPayload: OCPP16AuthorizeRequest |
e7aeea18 | 453 | ): void { |
66a7748d | 454 | let authorizeConnectorId: number | undefined |
ded57f02 JB |
455 | if (chargingStation.hasEvses) { |
456 | for (const [evseId, evseStatus] of chargingStation.evses) { | |
457 | if (evseId > 0) { | |
458 | for (const [connectorId, connectorStatus] of evseStatus.connectors) { | |
5199f9fd | 459 | if (connectorStatus.authorizeIdTag === requestPayload.idTag) { |
66a7748d JB |
460 | authorizeConnectorId = connectorId |
461 | break | |
ded57f02 JB |
462 | } |
463 | } | |
464 | } | |
465 | } | |
466 | } else { | |
467 | for (const connectorId of chargingStation.connectors.keys()) { | |
468 | if ( | |
469 | connectorId > 0 && | |
470 | chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag | |
471 | ) { | |
66a7748d JB |
472 | authorizeConnectorId = connectorId |
473 | break | |
ded57f02 | 474 | } |
58144adb JB |
475 | } |
476 | } | |
f938317f JB |
477 | if (authorizeConnectorId != null) { |
478 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
479 | const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)! | |
480 | if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) { | |
481 | authorizeConnectorStatus.idTagAuthorized = true | |
482 | logger.debug( | |
483 | `${chargingStation.logPrefix()} idTag '${ | |
484 | requestPayload.idTag | |
d1f5bfd8 | 485 | }' accepted on connector id ${authorizeConnectorId.toString()}` |
f938317f JB |
486 | ) |
487 | } else { | |
488 | authorizeConnectorStatus.idTagAuthorized = false | |
489 | delete authorizeConnectorStatus.authorizeIdTag | |
490 | logger.debug( | |
48847bc0 JB |
491 | `${chargingStation.logPrefix()} idTag '${ |
492 | requestPayload.idTag | |
493 | }' rejected with status '${payload.idTagInfo.status}'` | |
f938317f | 494 | ) |
d984c13f | 495 | } |
58144adb | 496 | } else { |
f938317f JB |
497 | logger.error( |
498 | `${chargingStation.logPrefix()} idTag '${ | |
499 | requestPayload.idTag | |
500 | }' has no authorize request pending` | |
66a7748d | 501 | ) |
58144adb JB |
502 | } |
503 | } | |
504 | ||
0749233f JB |
505 | private handleResponseBootNotification ( |
506 | chargingStation: ChargingStation, | |
507 | payload: OCPP16BootNotificationResponse | |
508 | ): void { | |
509 | if (Object.values(RegistrationStatusEnumType).includes(payload.status)) { | |
510 | chargingStation.bootNotificationResponse = payload | |
db7e450e JB |
511 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition |
512 | if (payload.interval != null) { | |
513 | addConfigurationKey( | |
514 | chargingStation, | |
515 | OCPP16StandardParametersKey.HeartbeatInterval, | |
516 | payload.interval.toString(), | |
517 | {}, | |
518 | { overwrite: true, save: true } | |
519 | ) | |
520 | addConfigurationKey( | |
521 | chargingStation, | |
522 | OCPP16StandardParametersKey.HeartBeatInterval, | |
523 | payload.interval.toString(), | |
524 | { visible: false }, | |
525 | { overwrite: true, save: true } | |
526 | ) | |
527 | } | |
b72d1749 | 528 | if (chargingStation.inAcceptedState()) { |
b72d1749 JB |
529 | chargingStation.emit(ChargingStationEvents.accepted) |
530 | } else if (chargingStation.inPendingState()) { | |
531 | chargingStation.emit(ChargingStationEvents.pending) | |
0749233f JB |
532 | } else if (chargingStation.inRejectedState()) { |
533 | chargingStation.emit(ChargingStationEvents.rejected) | |
534 | } | |
535 | const logMsg = `${chargingStation.logPrefix()} Charging station in '${ | |
536 | payload.status | |
537 | }' state on the central server` | |
538 | payload.status === RegistrationStatusEnumType.REJECTED | |
539 | ? logger.warn(logMsg) | |
540 | : logger.info(logMsg) | |
541 | } else { | |
542 | delete chargingStation.bootNotificationResponse | |
543 | logger.error( | |
544 | `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`, | |
545 | payload | |
546 | ) | |
547 | } | |
548 | } | |
549 | ||
66a7748d | 550 | private async handleResponseStartTransaction ( |
08f130a0 | 551 | chargingStation: ChargingStation, |
e7aeea18 | 552 | payload: OCPP16StartTransactionResponse, |
66a7748d | 553 | requestPayload: OCPP16StartTransactionRequest |
e7aeea18 | 554 | ): Promise<void> { |
66a7748d JB |
555 | const { connectorId } = requestPayload |
556 | if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) { | |
e7aeea18 | 557 | logger.error( |
d1f5bfd8 | 558 | `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId.toString()}` |
66a7748d JB |
559 | ) |
560 | return | |
c0560973 | 561 | } |
66a7748d | 562 | const connectorStatus = chargingStation.getConnectorStatus(connectorId) |
e7aeea18 | 563 | if ( |
d929adcc | 564 | connectorStatus?.transactionRemoteStarted === true && |
66a7748d JB |
565 | chargingStation.getAuthorizeRemoteTxRequests() && |
566 | chargingStation.getLocalAuthListEnabled() && | |
567 | chargingStation.hasIdTags() && | |
5199f9fd | 568 | connectorStatus.idTagLocalAuthorized === false |
e7aeea18 JB |
569 | ) { |
570 | logger.error( | |
5199f9fd | 571 | `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${ |
d1f5bfd8 | 572 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
5199f9fd | 573 | connectorStatus.localAuthorizeIdTag |
d1f5bfd8 | 574 | } on connector id ${connectorId.toString()}` |
66a7748d JB |
575 | ) |
576 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId) | |
577 | return | |
a2653482 | 578 | } |
e7aeea18 | 579 | if ( |
d929adcc | 580 | connectorStatus?.transactionRemoteStarted === true && |
66a7748d | 581 | chargingStation.getAuthorizeRemoteTxRequests() && |
5398cecf | 582 | chargingStation.stationInfo?.remoteAuthorization === true && |
5199f9fd JB |
583 | connectorStatus.idTagLocalAuthorized === false && |
584 | connectorStatus.idTagAuthorized === false | |
e7aeea18 JB |
585 | ) { |
586 | logger.error( | |
5199f9fd | 587 | `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${ |
d1f5bfd8 | 588 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
5199f9fd | 589 | connectorStatus.authorizeIdTag |
d1f5bfd8 | 590 | } on connector id ${connectorId.toString()}` |
66a7748d JB |
591 | ) |
592 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId) | |
593 | return | |
a2653482 | 594 | } |
e7aeea18 | 595 | if ( |
66a7748d | 596 | connectorStatus?.idTagAuthorized === true && |
ae8fb16d | 597 | connectorStatus.authorizeIdTag != null && |
5199f9fd | 598 | connectorStatus.authorizeIdTag !== requestPayload.idTag |
e7aeea18 JB |
599 | ) { |
600 | logger.error( | |
44eb6026 JB |
601 | `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${ |
602 | requestPayload.idTag | |
5199f9fd JB |
603 | } different from the authorize request one ${ |
604 | connectorStatus.authorizeIdTag | |
d1f5bfd8 | 605 | } on connector id ${connectorId.toString()}` |
66a7748d JB |
606 | ) |
607 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId) | |
608 | return | |
a2653482 | 609 | } |
e7aeea18 | 610 | if ( |
66a7748d | 611 | connectorStatus?.idTagLocalAuthorized === true && |
ae8fb16d | 612 | connectorStatus.localAuthorizeIdTag != null && |
5199f9fd | 613 | connectorStatus.localAuthorizeIdTag !== requestPayload.idTag |
e7aeea18 JB |
614 | ) { |
615 | logger.error( | |
44eb6026 JB |
616 | `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${ |
617 | requestPayload.idTag | |
5199f9fd JB |
618 | } different from the local authorized one ${ |
619 | connectorStatus.localAuthorizeIdTag | |
d1f5bfd8 | 620 | } on connector id ${connectorId.toString()}` |
66a7748d JB |
621 | ) |
622 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId) | |
623 | return | |
163547b1 | 624 | } |
d929adcc | 625 | if (connectorStatus?.transactionStarted === true) { |
649287f8 | 626 | logger.error( |
d1f5bfd8 JB |
627 | `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId.toString()} by idTag ${ |
628 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
5199f9fd JB |
629 | connectorStatus.transactionIdTag |
630 | }` | |
66a7748d JB |
631 | ) |
632 | return | |
c0560973 | 633 | } |
649287f8 JB |
634 | if (chargingStation.hasEvses) { |
635 | for (const [evseId, evseStatus] of chargingStation.evses) { | |
636 | if (evseStatus.connectors.size > 1) { | |
d929adcc | 637 | for (const [id, status] of evseStatus.connectors) { |
5199f9fd | 638 | if (id !== connectorId && status.transactionStarted === true) { |
649287f8 | 639 | logger.error( |
d1f5bfd8 JB |
640 | `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId.toString()} by connector id ${id.toString()} with idTag ${ |
641 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
5199f9fd JB |
642 | status.transactionIdTag |
643 | }` | |
66a7748d JB |
644 | ) |
645 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId) | |
646 | return | |
649287f8 JB |
647 | } |
648 | } | |
649 | } | |
650 | } | |
651 | } | |
e7aeea18 | 652 | if ( |
d929adcc JB |
653 | connectorStatus?.status !== OCPP16ChargePointStatus.Available && |
654 | connectorStatus?.status !== OCPP16ChargePointStatus.Preparing | |
e7aeea18 JB |
655 | ) { |
656 | logger.error( | |
d1f5bfd8 JB |
657 | `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId.toString()} with status ${ |
658 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
48847bc0 JB |
659 | connectorStatus?.status |
660 | }` | |
66a7748d JB |
661 | ) |
662 | return | |
290d006c | 663 | } |
d1504492 | 664 | if (!Number.isSafeInteger(payload.transactionId)) { |
54ebb82c | 665 | logger.warn( |
a743807e | 666 | `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId.toString()} with a non integer transaction id ${payload.transactionId.toString()}, converting to integer` |
66a7748d JB |
667 | ) |
668 | payload.transactionId = convertToInt(payload.transactionId) | |
54ebb82c | 669 | } |
c0560973 | 670 | |
5199f9fd | 671 | if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) { |
66a7748d JB |
672 | connectorStatus.transactionStarted = true |
673 | connectorStatus.transactionStart = requestPayload.timestamp | |
674 | connectorStatus.transactionId = payload.transactionId | |
675 | connectorStatus.transactionIdTag = requestPayload.idTag | |
676 | connectorStatus.transactionEnergyActiveImportRegisterValue = 0 | |
d929adcc | 677 | connectorStatus.transactionBeginMeterValue = |
e7aeea18 | 678 | OCPP16ServiceUtils.buildTransactionBeginMeterValue( |
08f130a0 | 679 | chargingStation, |
d929adcc | 680 | connectorId, |
66a7748d JB |
681 | requestPayload.meterStart |
682 | ) | |
683 | if (requestPayload.reservationId != null) { | |
90aceaf6 JB |
684 | const reservation = chargingStation.getReservationBy( |
685 | 'reservationId', | |
66a7748d | 686 | requestPayload.reservationId |
a095d7d7 JB |
687 | ) |
688 | if (reservation != null) { | |
689 | if (reservation.idTag !== requestPayload.idTag) { | |
690 | logger.warn( | |
a743807e | 691 | `${chargingStation.logPrefix()} Reserved transaction ${payload.transactionId.toString()} started with a different idTag ${ |
48847bc0 JB |
692 | requestPayload.idTag |
693 | } than the reservation one ${reservation.idTag}` | |
a095d7d7 JB |
694 | ) |
695 | } | |
696 | if (hasReservationExpired(reservation)) { | |
697 | logger.warn( | |
a743807e | 698 | `${chargingStation.logPrefix()} Reserved transaction ${payload.transactionId.toString()} started with expired reservation ${requestPayload.reservationId.toString()} (expiry date: ${reservation.expiryDate.toISOString()}))` |
a095d7d7 JB |
699 | ) |
700 | } | |
701 | await chargingStation.removeReservation( | |
702 | reservation, | |
703 | ReservationTerminationReason.TRANSACTION_STARTED | |
66a7748d | 704 | ) |
a095d7d7 | 705 | } else { |
90aceaf6 | 706 | logger.warn( |
a743807e | 707 | `${chargingStation.logPrefix()} Reserved transaction ${payload.transactionId.toString()} started with unknown reservation ${requestPayload.reservationId.toString()}` |
66a7748d | 708 | ) |
90aceaf6 | 709 | } |
d984c13f | 710 | } |
66a7748d | 711 | chargingStation.stationInfo?.beginEndMeterValues === true && |
08f130a0 | 712 | (await chargingStation.ocppRequestService.requestHandler< |
d1f5bfd8 JB |
713 | OCPP16MeterValuesRequest, |
714 | OCPP16MeterValuesResponse | |
08f130a0 | 715 | >(chargingStation, OCPP16RequestCommand.METER_VALUES, { |
d929adcc | 716 | connectorId, |
d1f5bfd8 | 717 | meterValue: [connectorStatus.transactionBeginMeterValue], |
0749233f | 718 | transactionId: payload.transactionId, |
66a7748d | 719 | } satisfies OCPP16MeterValuesRequest)) |
4ecff7ce JB |
720 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
721 | chargingStation, | |
d929adcc | 722 | connectorId, |
66a7748d JB |
723 | OCPP16ChargePointStatus.Charging |
724 | ) | |
e7aeea18 | 725 | logger.info( |
d1f5bfd8 JB |
726 | `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId.toString()} STARTED on ${ |
727 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
48847bc0 | 728 | chargingStation.stationInfo?.chargingStationId |
d1f5bfd8 | 729 | }#${connectorId.toString()} for idTag '${requestPayload.idTag}'` |
66a7748d | 730 | ) |
5199f9fd JB |
731 | if (chargingStation.stationInfo?.powerSharedByConnectors === true) { |
732 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
733 | ++chargingStation.powerDivider! | |
c0560973 | 734 | } |
f2d5e3d9 JB |
735 | const configuredMeterValueSampleInterval = getConfigurationKey( |
736 | chargingStation, | |
66a7748d JB |
737 | OCPP16StandardParametersKey.MeterValueSampleInterval |
738 | ) | |
08f130a0 | 739 | chargingStation.startMeterValues( |
d929adcc | 740 | connectorId, |
a807045b | 741 | configuredMeterValueSampleInterval != null |
be4c6702 | 742 | ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value)) |
66a7748d JB |
743 | : Constants.DEFAULT_METER_VALUES_INTERVAL |
744 | ) | |
c0560973 | 745 | } else { |
e7aeea18 | 746 | logger.warn( |
a743807e | 747 | `${chargingStation.logPrefix()} Starting transaction with id ${payload.transactionId.toString()} REJECTED on ${ |
d1f5bfd8 | 748 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
a223d9be | 749 | chargingStation.stationInfo?.chargingStationId |
d1f5bfd8 | 750 | }#${connectorId.toString()} with status '${payload.idTagInfo.status}', idTag '${ |
56563a3c JB |
751 | requestPayload.idTag |
752 | }'${ | |
d929adcc | 753 | OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag) |
a743807e JB |
754 | ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
755 | `, reservationId '${requestPayload.reservationId?.toString()}'` | |
56563a3c | 756 | : '' |
66a7748d JB |
757 | }` |
758 | ) | |
759 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId) | |
a2653482 JB |
760 | } |
761 | } | |
762 | ||
66a7748d | 763 | private async handleResponseStopTransaction ( |
08f130a0 | 764 | chargingStation: ChargingStation, |
e7aeea18 | 765 | payload: OCPP16StopTransactionResponse, |
66a7748d | 766 | requestPayload: OCPP16StopTransactionRequest |
e7aeea18 | 767 | ): Promise<void> { |
08f130a0 | 768 | const transactionConnectorId = chargingStation.getConnectorIdByTransactionId( |
66a7748d JB |
769 | requestPayload.transactionId |
770 | ) | |
aa63c9b7 | 771 | if (transactionConnectorId == null) { |
e7aeea18 | 772 | logger.error( |
a743807e | 773 | `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${requestPayload.transactionId.toString()}` |
66a7748d JB |
774 | ) |
775 | return | |
c0560973 | 776 | } |
5398cecf | 777 | chargingStation.stationInfo?.beginEndMeterValues === true && |
5199f9fd JB |
778 | chargingStation.stationInfo.ocppStrictCompliance === false && |
779 | chargingStation.stationInfo.outOfOrderEndMeterValues === true && | |
2cace1a5 | 780 | (await chargingStation.ocppRequestService.requestHandler< |
d1f5bfd8 JB |
781 | OCPP16MeterValuesRequest, |
782 | OCPP16MeterValuesResponse | |
2cace1a5 JB |
783 | >(chargingStation, OCPP16RequestCommand.METER_VALUES, { |
784 | connectorId: transactionConnectorId, | |
2cace1a5 JB |
785 | meterValue: [ |
786 | OCPP16ServiceUtils.buildTransactionEndMeterValue( | |
787 | chargingStation, | |
aa63c9b7 | 788 | transactionConnectorId, |
66a7748d | 789 | requestPayload.meterStop |
d1f5bfd8 JB |
790 | ), |
791 | ], | |
0749233f | 792 | transactionId: requestPayload.transactionId, |
66a7748d | 793 | })) |
2cace1a5 | 794 | if ( |
66a7748d | 795 | !chargingStation.isChargingStationAvailable() || |
aa63c9b7 | 796 | !chargingStation.isConnectorAvailable(transactionConnectorId) |
2cace1a5 | 797 | ) { |
4ecff7ce JB |
798 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
799 | chargingStation, | |
aa63c9b7 | 800 | transactionConnectorId, |
66a7748d JB |
801 | OCPP16ChargePointStatus.Unavailable |
802 | ) | |
c0560973 | 803 | } else { |
4ecff7ce JB |
804 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
805 | chargingStation, | |
aa63c9b7 | 806 | transactionConnectorId, |
66a7748d JB |
807 | OCPP16ChargePointStatus.Available |
808 | ) | |
2cace1a5 | 809 | } |
5199f9fd JB |
810 | if (chargingStation.stationInfo?.powerSharedByConnectors === true) { |
811 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
812 | chargingStation.powerDivider!-- | |
2cace1a5 | 813 | } |
f938317f | 814 | resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId)) |
aa63c9b7 | 815 | chargingStation.stopMeterValues(transactionConnectorId) |
a743807e | 816 | const logMsg = `${chargingStation.logPrefix()} Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${ |
d1f5bfd8 | 817 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
a223d9be | 818 | chargingStation.stationInfo?.chargingStationId |
a743807e | 819 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
d1f5bfd8 | 820 | }#${transactionConnectorId.toString()} with status '${payload.idTagInfo?.status}'` |
2cace1a5 | 821 | if ( |
aa63c9b7 | 822 | payload.idTagInfo == null || |
5199f9fd | 823 | payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED |
2cace1a5 | 824 | ) { |
66a7748d | 825 | logger.info(logMsg) |
2cace1a5 | 826 | } else { |
66a7748d | 827 | logger.warn(logMsg) |
c0560973 JB |
828 | } |
829 | } | |
0749233f JB |
830 | |
831 | private async resetConnectorOnStartTransactionError ( | |
832 | chargingStation: ChargingStation, | |
833 | connectorId: number | |
834 | ): Promise<void> { | |
835 | chargingStation.stopMeterValues(connectorId) | |
836 | const connectorStatus = chargingStation.getConnectorStatus(connectorId) | |
837 | resetConnectorStatus(connectorStatus) | |
838 | await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus) | |
839 | } | |
840 | ||
841 | private validatePayload ( | |
842 | chargingStation: ChargingStation, | |
843 | commandName: OCPP16RequestCommand, | |
844 | payload: JsonType | |
845 | ): boolean { | |
846 | if (this.payloadValidateFunctions.has(commandName)) { | |
847 | return this.validateResponsePayload(chargingStation, commandName, payload) | |
848 | } | |
849 | logger.warn( | |
850 | `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation` | |
851 | ) | |
852 | return false | |
853 | } | |
c0560973 | 854 | } |