fix: ensure the second stage at handling incoming request is executed
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
c8eeb62b 2
66a7748d
JB
3import { createWriteStream, readdirSync } from 'node:fs'
4import { dirname, join, resolve } from 'node:path'
5import { URL, fileURLToPath } from 'node:url'
8114d10e 6
24d15716 7import type { ValidateFunction } from 'ajv'
66a7748d 8import { Client, type FTPResponse } from 'basic-ftp'
f1e3871b
JB
9import {
10 type Interval,
11 addSeconds,
12 differenceInSeconds,
13 isDate,
66a7748d
JB
14 secondsToMilliseconds
15} from 'date-fns'
16import { maxTime } from 'date-fns/constants'
17import { create } from 'tar'
8114d10e 18
66a7748d
JB
19import { OCPP16Constants } from './OCPP16Constants.js'
20import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
2896e06d
JB
21import {
22 type ChargingStation,
ad490d5f 23 canProceedChargingProfile,
fba11dc6 24 checkChargingStation,
f2d5e3d9 25 getConfigurationKey,
6fc0c6f3 26 getConnectorChargingProfiles,
0eb666db 27 prepareChargingProfileKind,
90aceaf6 28 removeExpiredReservations,
66a7748d
JB
29 setConfigurationKeyValue
30} from '../../../charging-station/index.js'
31import { OCPPError } from '../../../exception/index.js'
e7aeea18 32import {
27782dbc 33 type ChangeConfigurationRequest,
268a74bb 34 type ChangeConfigurationResponse,
268a74bb
JB
35 ErrorType,
36 type GenericResponse,
41189456 37 GenericStatus,
27782dbc 38 type GetConfigurationRequest,
268a74bb 39 type GetConfigurationResponse,
27782dbc 40 type GetDiagnosticsRequest,
268a74bb
JB
41 type GetDiagnosticsResponse,
42 type IncomingRequestHandler,
268a74bb
JB
43 type JsonType,
44 OCPP16AuthorizationStatus,
e7aeea18 45 OCPP16AvailabilityType,
27782dbc 46 type OCPP16BootNotificationRequest,
268a74bb 47 type OCPP16BootNotificationResponse,
66dd3447 48 type OCPP16CancelReservationRequest,
366f75f6
JB
49 type OCPP16ChangeAvailabilityRequest,
50 type OCPP16ChangeAvailabilityResponse,
268a74bb
JB
51 OCPP16ChargePointErrorCode,
52 OCPP16ChargePointStatus,
53 type OCPP16ChargingProfile,
0ac97927 54 OCPP16ChargingProfilePurposeType,
41189456 55 type OCPP16ChargingSchedule,
27782dbc 56 type OCPP16ClearCacheRequest,
41f3983a
JB
57 type OCPP16ClearChargingProfileRequest,
58 type OCPP16ClearChargingProfileResponse,
27782dbc 59 type OCPP16DataTransferRequest,
268a74bb 60 type OCPP16DataTransferResponse,
77b95a89 61 OCPP16DataTransferVendorId,
268a74bb 62 OCPP16DiagnosticsStatus,
c9a4f9ea 63 type OCPP16DiagnosticsStatusNotificationRequest,
268a74bb 64 type OCPP16DiagnosticsStatusNotificationResponse,
c9a4f9ea
JB
65 OCPP16FirmwareStatus,
66 type OCPP16FirmwareStatusNotificationRequest,
268a74bb 67 type OCPP16FirmwareStatusNotificationResponse,
41189456
JB
68 type OCPP16GetCompositeScheduleRequest,
69 type OCPP16GetCompositeScheduleResponse,
27782dbc 70 type OCPP16HeartbeatRequest,
268a74bb 71 type OCPP16HeartbeatResponse,
e7aeea18 72 OCPP16IncomingRequestCommand,
c60ed4b8 73 OCPP16MessageTrigger,
94a464f9 74 OCPP16RequestCommand,
66dd3447
JB
75 type OCPP16ReserveNowRequest,
76 type OCPP16ReserveNowResponse,
268a74bb
JB
77 OCPP16StandardParametersKey,
78 type OCPP16StartTransactionRequest,
79 type OCPP16StartTransactionResponse,
27782dbc 80 type OCPP16StatusNotificationRequest,
268a74bb
JB
81 type OCPP16StatusNotificationResponse,
82 OCPP16StopTransactionReason,
83 OCPP16SupportedFeatureProfiles,
27782dbc 84 type OCPP16TriggerMessageRequest,
268a74bb 85 type OCPP16TriggerMessageResponse,
ef69bc46 86 OCPP16TriggerMessageStatus,
27782dbc 87 type OCPP16UpdateFirmwareRequest,
268a74bb
JB
88 type OCPP16UpdateFirmwareResponse,
89 type OCPPConfigurationKey,
90 OCPPVersion,
27782dbc
JB
91 type RemoteStartTransactionRequest,
92 type RemoteStopTransactionRequest,
66dd3447 93 ReservationTerminationReason,
27782dbc
JB
94 type ResetRequest,
95 type SetChargingProfileRequest,
27782dbc 96 type SetChargingProfileResponse,
268a74bb 97 type UnlockConnectorRequest,
66a7748d
JB
98 type UnlockConnectorResponse
99} from '../../../types/index.js'
9bf0ef23
JB
100import {
101 Constants,
102 convertToDate,
103 convertToInt,
104 formatDurationMilliSeconds,
105 getRandomInteger,
106 isEmptyArray,
107 isNotEmptyArray,
108 isNotEmptyString,
9bf0ef23 109 logger,
66a7748d
JB
110 sleep
111} from '../../../utils/index.js'
112import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
c0560973 113
66a7748d 114const moduleName = 'OCPP16IncomingRequestService'
909dcf2d 115
268a74bb 116export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
d5490a13 117 protected payloadValidateFunctions: Map<OCPP16IncomingRequestCommand, ValidateFunction<JsonType>>
24d15716 118
66a7748d
JB
119 private readonly incomingRequestHandlers: Map<
120 OCPP16IncomingRequestCommand,
121 IncomingRequestHandler
122 >
58144adb 123
66a7748d 124 public constructor () {
5199f9fd
JB
125 // if (new.target.name === moduleName) {
126 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
b768993d 127 // }
66a7748d 128 super(OCPPVersion.VERSION_16)
58144adb 129 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
a37fc6dc
JB
130 [
131 OCPP16IncomingRequestCommand.RESET,
66a7748d 132 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler
a37fc6dc
JB
133 ],
134 [
135 OCPP16IncomingRequestCommand.CLEAR_CACHE,
66a7748d 136 this.handleRequestClearCache.bind(this) as IncomingRequestHandler
a37fc6dc
JB
137 ],
138 [
139 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
66a7748d 140 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler
a37fc6dc 141 ],
e7aeea18
JB
142 [
143 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
66a7748d 144 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler
e7aeea18
JB
145 ],
146 [
147 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
66a7748d 148 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler
e7aeea18 149 ],
41189456
JB
150 [
151 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
66a7748d 152 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler
41189456 153 ],
e7aeea18
JB
154 [
155 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
66a7748d 156 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler
e7aeea18
JB
157 ],
158 [
159 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
66a7748d 160 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler
e7aeea18
JB
161 ],
162 [
163 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
66a7748d 164 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler
e7aeea18
JB
165 ],
166 [
167 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
66a7748d 168 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler
e7aeea18
JB
169 ],
170 [
171 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
66a7748d 172 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler
a37fc6dc
JB
173 ],
174 [
175 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
66a7748d 176 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler
a37fc6dc
JB
177 ],
178 [
179 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d 180 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler
a37fc6dc
JB
181 ],
182 [
183 OCPP16IncomingRequestCommand.DATA_TRANSFER,
66a7748d 184 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler
a37fc6dc
JB
185 ],
186 [
187 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
66a7748d 188 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler
a37fc6dc
JB
189 ],
190 [
191 OCPP16IncomingRequestCommand.RESERVE_NOW,
66a7748d 192 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler
e7aeea18 193 ],
d193a949
JB
194 [
195 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
66a7748d
JB
196 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler
197 ]
198 ])
d5490a13 199 this.payloadValidateFunctions = new Map<
24d15716
JB
200 OCPP16IncomingRequestCommand,
201 ValidateFunction<JsonType>
202 >([
b52c969d
JB
203 [
204 OCPP16IncomingRequestCommand.RESET,
24d15716
JB
205 this.ajv
206 .compile(
207 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
208 'assets/json-schemas/ocpp/1.6/Reset.json',
209 moduleName,
210 'constructor'
211 )
212 )
213 .bind(this)
b52c969d
JB
214 ],
215 [
216 OCPP16IncomingRequestCommand.CLEAR_CACHE,
24d15716
JB
217 this.ajv
218 .compile(
219 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
220 'assets/json-schemas/ocpp/1.6/ClearCache.json',
221 moduleName,
222 'constructor'
223 )
224 )
225 .bind(this)
b52c969d
JB
226 ],
227 [
228 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
24d15716
JB
229 this.ajv
230 .compile(
231 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
232 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
233 moduleName,
234 'constructor'
235 )
236 )
237 .bind(this)
b52c969d
JB
238 ],
239 [
240 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
24d15716
JB
241 this.ajv
242 .compile(
243 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
244 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
245 moduleName,
246 'constructor'
247 )
248 )
249 .bind(this)
b52c969d
JB
250 ],
251 [
252 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
24d15716
JB
253 this.ajv
254 .compile(
255 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
256 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
257 moduleName,
258 'constructor'
259 )
260 )
261 .bind(this)
b52c969d
JB
262 ],
263 [
264 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
24d15716
JB
265 this.ajv
266 .compile(
267 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
268 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
269 moduleName,
270 'constructor'
271 )
272 )
273 .bind(this)
b52c969d 274 ],
41189456
JB
275 [
276 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
24d15716
JB
277 this.ajv
278 .compile(
279 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
280 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
281 moduleName,
282 'constructor'
283 )
284 )
285 .bind(this)
41189456 286 ],
b52c969d
JB
287 [
288 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
24d15716
JB
289 this.ajv
290 .compile(
291 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
292 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
293 moduleName,
294 'constructor'
295 )
296 )
297 .bind(this)
b52c969d
JB
298 ],
299 [
300 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
24d15716
JB
301 this.ajv
302 .compile(
303 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
304 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
305 moduleName,
306 'constructor'
307 )
308 )
309 .bind(this)
b52c969d
JB
310 ],
311 [
312 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
24d15716
JB
313 this.ajv
314 .compile(
315 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
316 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
317 moduleName,
318 'constructor'
319 )
320 )
321 .bind(this)
b52c969d
JB
322 ],
323 [
324 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
24d15716
JB
325 this.ajv
326 .compile(
327 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
328 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
329 moduleName,
330 'constructor'
331 )
332 )
333 .bind(this)
b52c969d
JB
334 ],
335 [
336 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
24d15716
JB
337 this.ajv
338 .compile(
339 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
340 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
341 moduleName,
342 'constructor'
343 )
344 )
345 .bind(this)
b52c969d
JB
346 ],
347 [
348 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
24d15716
JB
349 this.ajv
350 .compile(
351 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
352 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
353 moduleName,
354 'constructor'
355 )
356 )
357 .bind(this)
b52c969d 358 ],
77b95a89
JB
359 [
360 OCPP16IncomingRequestCommand.DATA_TRANSFER,
24d15716
JB
361 this.ajv
362 .compile(
363 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
364 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
365 moduleName,
366 'constructor'
367 )
368 )
369 .bind(this)
77b95a89 370 ],
bfbda738
JB
371 [
372 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
24d15716
JB
373 this.ajv
374 .compile(
375 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
376 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
377 moduleName,
378 'constructor'
379 )
380 )
381 .bind(this)
bfbda738 382 ],
d193a949
JB
383 [
384 OCPP16IncomingRequestCommand.RESERVE_NOW,
24d15716
JB
385 this.ajv
386 .compile(
387 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
388 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
389 moduleName,
390 'constructor'
391 )
392 )
393 .bind(this)
d193a949
JB
394 ],
395 [
396 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
24d15716
JB
397 this.ajv
398 .compile(
399 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
400 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
401 moduleName,
402 'constructor'
403 )
404 )
405 .bind(this)
66a7748d
JB
406 ]
407 ])
54510a60
JB
408 this.on(
409 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
1b4a545f
JB
410 (
411 chargingStation: ChargingStation,
412 request: RemoteStartTransactionRequest,
413 response: GenericResponse
414 ) => {
415 if (response.status === GenericStatus.Accepted) {
416 const { connectorId, idTag } = request
417 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
418 chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true
419 chargingStation.ocppRequestService
420 .requestHandler<OCPP16StartTransactionRequest, OCPP16StartTransactionResponse>(
421 chargingStation,
422 OCPP16RequestCommand.START_TRANSACTION,
423 {
424 connectorId,
425 idTag
54510a60 426 }
1b4a545f
JB
427 )
428 .then(response => {
429 if (response.status === OCPP16AuthorizationStatus.ACCEPTED) {
430 logger.debug(
431 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
432 )
433 } else {
434 logger.debug(
435 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
436 )
437 }
438 })
439 .catch(error => {
440 logger.error(
441 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
442 error
443 )
444 })
445 }
5c24bae9
JB
446 }
447 )
5c24bae9 448 this.on(
1b4a545f 449 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
ef69bc46
JB
450 (
451 chargingStation: ChargingStation,
452 request: OCPP16TriggerMessageRequest,
453 response: OCPP16TriggerMessageResponse
454 ) => {
455 if (response.status !== OCPP16TriggerMessageStatus.ACCEPTED) {
456 return
457 }
1b4a545f 458 const { requestedMessage, connectorId } = request
5c24bae9
JB
459 const errorHandler = (error: Error): void => {
460 logger.error(
1b4a545f 461 `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
5c24bae9
JB
462 error
463 )
464 }
1b4a545f
JB
465 switch (requestedMessage) {
466 case OCPP16MessageTrigger.BootNotification:
467 chargingStation.ocppRequestService
468 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
469 chargingStation,
470 OCPP16RequestCommand.BOOT_NOTIFICATION,
471 chargingStation.bootNotificationRequest,
472 { skipBufferingOnError: true, triggerMessage: true }
473 )
474 .then(response => {
475 chargingStation.bootNotificationResponse = response
476 })
477 .catch(errorHandler)
478 break
479 case OCPP16MessageTrigger.Heartbeat:
480 chargingStation.ocppRequestService
481 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
482 chargingStation,
483 OCPP16RequestCommand.HEARTBEAT,
484 undefined,
485 {
486 triggerMessage: true
487 }
488 )
489 .catch(errorHandler)
490 break
491 case OCPP16MessageTrigger.StatusNotification:
492 if (connectorId != null) {
5c24bae9
JB
493 chargingStation.ocppRequestService
494 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
495 chargingStation,
496 OCPP16RequestCommand.STATUS_NOTIFICATION,
497 {
1b4a545f 498 connectorId,
5c24bae9 499 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1b4a545f 500 status: chargingStation.getConnectorStatus(connectorId)?.status
5c24bae9
JB
501 },
502 {
503 triggerMessage: true
504 }
505 )
506 .catch(errorHandler)
1b4a545f
JB
507 } else if (chargingStation.hasEvses) {
508 for (const evseStatus of chargingStation.evses.values()) {
509 for (const [id, connectorStatus] of evseStatus.connectors) {
510 chargingStation.ocppRequestService
511 .requestHandler<
512 OCPP16StatusNotificationRequest,
513 OCPP16StatusNotificationResponse
514 >(
515 chargingStation,
516 OCPP16RequestCommand.STATUS_NOTIFICATION,
517 {
518 connectorId: id,
519 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
520 status: connectorStatus.status
521 },
522 {
523 triggerMessage: true
524 }
525 )
526 .catch(errorHandler)
527 }
5c24bae9 528 }
1b4a545f
JB
529 } else {
530 for (const [id, connectorStatus] of chargingStation.connectors) {
531 chargingStation.ocppRequestService
532 .requestHandler<
533 OCPP16StatusNotificationRequest,
534 OCPP16StatusNotificationResponse
535 >(
536 chargingStation,
537 OCPP16RequestCommand.STATUS_NOTIFICATION,
538 {
539 connectorId: id,
540 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
541 status: connectorStatus.status
542 },
543 {
544 triggerMessage: true
545 }
546 )
547 .catch(errorHandler)
548 }
549 }
550 break
5c24bae9
JB
551 }
552 }
553 )
ba9a56a6 554 this.validatePayload = this.validatePayload.bind(this)
58144adb
JB
555 }
556
9429aa42 557 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 558 chargingStation: ChargingStation,
e7aeea18
JB
559 messageId: string,
560 commandName: OCPP16IncomingRequestCommand,
66a7748d 561 commandPayload: ReqType
e7aeea18 562 ): Promise<void> {
66a7748d 563 let response: ResType
e7aeea18 564 if (
5398cecf 565 chargingStation.stationInfo?.ocppStrictCompliance === true &&
66a7748d 566 chargingStation.inPendingState() &&
e7aeea18
JB
567 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
568 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
569 ) {
570 throw new OCPPError(
571 ErrorType.SECURITY_ERROR,
e3018bc4 572 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18 573 commandPayload,
4ed03b6e 574 undefined,
66a7748d 575 2
e7aeea18 576 )} while the charging station is in pending state on the central server`,
7369e417 577 commandName,
66a7748d
JB
578 commandPayload
579 )
caad9d6b 580 }
e7aeea18 581 if (
66a7748d 582 chargingStation.isRegistered() ||
5398cecf 583 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
66a7748d 584 chargingStation.inUnknownState())
e7aeea18 585 ) {
65554cc3 586 if (
66a7748d
JB
587 this.incomingRequestHandlers.has(commandName) &&
588 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
65554cc3 589 ) {
124f3553 590 try {
66a7748d 591 this.validatePayload(chargingStation, commandName, commandPayload)
c75a6675 592 // Call the method to build the response
66a7748d 593 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9429aa42 594 response = (await this.incomingRequestHandlers.get(commandName)!(
08f130a0 595 chargingStation,
66a7748d
JB
596 commandPayload
597 )) as ResType
124f3553
JB
598 } catch (error) {
599 // Log
6c8f5d90 600 logger.error(
944d4529 601 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
66a7748d
JB
602 error
603 )
604 throw error
124f3553
JB
605 }
606 } else {
607 // Throw exception
e7aeea18
JB
608 throw new OCPPError(
609 ErrorType.NOT_IMPLEMENTED,
e3018bc4 610 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
e7aeea18 611 commandPayload,
4ed03b6e 612 undefined,
66a7748d 613 2
e7aeea18 614 )}`,
7369e417 615 commandName,
66a7748d
JB
616 commandPayload
617 )
c0560973
JB
618 }
619 } else {
e7aeea18
JB
620 throw new OCPPError(
621 ErrorType.SECURITY_ERROR,
e3018bc4 622 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18 623 commandPayload,
4ed03b6e 624 undefined,
66a7748d 625 2
e7aeea18 626 )} while the charging station is not registered on the central server.`,
7369e417 627 commandName,
66a7748d
JB
628 commandPayload
629 )
c0560973 630 }
c75a6675 631 // Send the built response
08f130a0
JB
632 await chargingStation.ocppRequestService.sendResponse(
633 chargingStation,
634 messageId,
635 response,
66a7748d
JB
636 commandName
637 )
1b7eb386 638 this.emit(commandName, chargingStation, commandPayload, response)
c0560973
JB
639 }
640
66a7748d 641 private validatePayload (
9c5c4195
JB
642 chargingStation: ChargingStation,
643 commandName: OCPP16IncomingRequestCommand,
66a7748d 644 commandPayload: JsonType
9c5c4195 645 ): boolean {
d5490a13 646 if (this.payloadValidateFunctions.has(commandName)) {
24d15716 647 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
9c5c4195
JB
648 }
649 logger.warn(
24d15716 650 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
651 )
652 return false
9c5c4195
JB
653 }
654
c0560973 655 // Simulate charging station restart
66a7748d 656 private handleRequestReset (
08f130a0 657 chargingStation: ChargingStation,
66a7748d 658 commandPayload: ResetRequest
f03e1042 659 ): GenericResponse {
66a7748d 660 const { type } = commandPayload
d1ff8599
JB
661 chargingStation
662 .reset(`${type}Reset` as OCPP16StopTransactionReason)
66a7748d 663 .catch(Constants.EMPTY_FUNCTION)
e7aeea18 664 logger.info(
944d4529 665 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
66a7748d 666 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5199f9fd 667 chargingStation.stationInfo!.resetTime!
66a7748d
JB
668 )}`
669 )
670 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
c0560973
JB
671 }
672
66a7748d 673 private async handleRequestUnlockConnector (
08f130a0 674 chargingStation: ChargingStation,
66a7748d 675 commandPayload: UnlockConnectorRequest
e7aeea18 676 ): Promise<UnlockConnectorResponse> {
66a7748d
JB
677 const { connectorId } = commandPayload
678 if (!chargingStation.hasConnector(connectorId)) {
c60ed4b8 679 logger.error(
66a7748d
JB
680 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
681 )
682 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
c60ed4b8 683 }
c0560973 684 if (connectorId === 0) {
66a7748d
JB
685 logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
686 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
c0560973 687 }
5e3cb728
JB
688 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
689 const stopResponse = await chargingStation.stopTransactionOnConnector(
690 connectorId,
66a7748d
JB
691 OCPP16StopTransactionReason.UNLOCK_COMMAND
692 )
c0560973 693 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d 694 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
c0560973 695 }
66a7748d 696 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
c0560973 697 }
4ecff7ce
JB
698 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
699 chargingStation,
ef6fa3fb 700 connectorId,
66a7748d
JB
701 OCPP16ChargePointStatus.Available
702 )
703 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
c0560973
JB
704 }
705
66a7748d 706 private handleRequestGetConfiguration (
08f130a0 707 chargingStation: ChargingStation,
66a7748d 708 commandPayload: GetConfigurationRequest
e7aeea18 709 ): GetConfigurationResponse {
66a7748d
JB
710 const { key } = commandPayload
711 const configurationKey: OCPPConfigurationKey[] = []
712 const unknownKey: string[] = []
300418e9 713 if (key == null) {
66a7748d 714 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563e40ce
JB
715 for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
716 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
66a7748d 717 continue
c0560973
JB
718 }
719 configurationKey.push({
563e40ce
JB
720 key: configKey.key,
721 readonly: configKey.readonly,
722 value: configKey.value
66a7748d 723 })
c0560973 724 }
66a7748d 725 } else if (isNotEmptyArray(key)) {
300418e9 726 for (const k of key) {
66a7748d 727 const keyFound = getConfigurationKey(chargingStation, k, true)
a807045b 728 if (keyFound != null) {
563e40ce 729 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
66a7748d 730 continue
c0560973
JB
731 }
732 configurationKey.push({
733 key: keyFound.key,
734 readonly: keyFound.readonly,
66a7748d
JB
735 value: keyFound.value
736 })
c0560973 737 } else {
66a7748d 738 unknownKey.push(k)
c0560973
JB
739 }
740 }
741 }
742 return {
743 configurationKey,
66a7748d
JB
744 unknownKey
745 }
c0560973
JB
746 }
747
66a7748d 748 private handleRequestChangeConfiguration (
08f130a0 749 chargingStation: ChargingStation,
66a7748d 750 commandPayload: ChangeConfigurationRequest
e7aeea18 751 ): ChangeConfigurationResponse {
66a7748d
JB
752 const { key, value } = commandPayload
753 const keyToChange = getConfigurationKey(chargingStation, key, true)
e1d9a0f4 754 if (keyToChange?.readonly === true) {
66a7748d 755 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED
bd5d98e0 756 } else if (keyToChange?.readonly === false) {
66a7748d 757 let valueChanged = false
0d1f33ba 758 if (keyToChange.value !== value) {
66a7748d
JB
759 setConfigurationKeyValue(chargingStation, key, value, true)
760 valueChanged = true
c0560973 761 }
66a7748d 762 let triggerHeartbeatRestart = false
e1d9a0f4
JB
763 if (
764 (keyToChange.key as OCPP16StandardParametersKey) ===
765 OCPP16StandardParametersKey.HeartBeatInterval &&
766 valueChanged
767 ) {
f2d5e3d9 768 setConfigurationKeyValue(
17ac262c 769 chargingStation,
e7aeea18 770 OCPP16StandardParametersKey.HeartbeatInterval,
66a7748d
JB
771 value
772 )
773 triggerHeartbeatRestart = true
c0560973 774 }
e1d9a0f4
JB
775 if (
776 (keyToChange.key as OCPP16StandardParametersKey) ===
777 OCPP16StandardParametersKey.HeartbeatInterval &&
778 valueChanged
779 ) {
f2d5e3d9 780 setConfigurationKeyValue(
17ac262c 781 chargingStation,
e7aeea18 782 OCPP16StandardParametersKey.HeartBeatInterval,
66a7748d
JB
783 value
784 )
785 triggerHeartbeatRestart = true
c0560973
JB
786 }
787 if (triggerHeartbeatRestart) {
66a7748d 788 chargingStation.restartHeartbeat()
c0560973 789 }
e1d9a0f4
JB
790 if (
791 (keyToChange.key as OCPP16StandardParametersKey) ===
792 OCPP16StandardParametersKey.WebSocketPingInterval &&
793 valueChanged
794 ) {
66a7748d 795 chargingStation.restartWebSocketPing()
c0560973 796 }
66a7748d
JB
797 if (keyToChange.reboot === true) {
798 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
c0560973 799 }
66a7748d 800 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
c0560973 801 }
66a7748d 802 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
c0560973
JB
803 }
804
66a7748d 805 private handleRequestSetChargingProfile (
08f130a0 806 chargingStation: ChargingStation,
66a7748d 807 commandPayload: SetChargingProfileRequest
e7aeea18 808 ): SetChargingProfileResponse {
370ae4ee 809 if (
66a7748d 810 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 811 chargingStation,
370ae4ee 812 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
813 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
814 )
370ae4ee 815 ) {
66a7748d 816 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
68cb8b91 817 }
66a7748d
JB
818 const { connectorId, csChargingProfiles } = commandPayload
819 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 820 logger.error(
66a7748d
JB
821 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
822 )
823 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 824 }
e7aeea18 825 if (
0d1f33ba 826 csChargingProfiles.chargingProfilePurpose ===
0ac97927 827 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
0d1f33ba 828 connectorId !== 0
e7aeea18 829 ) {
66a7748d 830 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 831 }
e7aeea18 832 if (
0d1f33ba 833 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
86f51b96
JB
834 connectorId === 0
835 ) {
836 logger.error(
66a7748d
JB
837 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
838 )
839 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
86f51b96 840 }
66a7748d 841 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
86f51b96
JB
842 if (
843 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
844 connectorId > 0 &&
845 connectorStatus?.transactionStarted === false
e7aeea18 846 ) {
db0af086 847 logger.error(
66a7748d
JB
848 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
849 )
850 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
c0560973 851 }
86f51b96
JB
852 if (
853 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
854 connectorId > 0 &&
855 connectorStatus?.transactionStarted === true &&
5199f9fd 856 csChargingProfiles.transactionId !== connectorStatus.transactionId
86f51b96
JB
857 ) {
858 logger.error(
944d4529
JB
859 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
860 csChargingProfiles.transactionId
5199f9fd 861 } than the started transaction id ${connectorStatus.transactionId}`
66a7748d
JB
862 )
863 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
86f51b96 864 }
66a7748d 865 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
e7aeea18 866 logger.debug(
0d1f33ba 867 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
66a7748d
JB
868 csChargingProfiles
869 )
870 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
871 }
872
66a7748d 873 private handleRequestGetCompositeSchedule (
41189456 874 chargingStation: ChargingStation,
66a7748d 875 commandPayload: OCPP16GetCompositeScheduleRequest
41189456
JB
876 ): OCPP16GetCompositeScheduleResponse {
877 if (
66a7748d 878 !OCPP16ServiceUtils.checkFeatureProfile(
41189456
JB
879 chargingStation,
880 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
881 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
882 )
41189456 883 ) {
66a7748d 884 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 885 }
66a7748d
JB
886 const { connectorId, duration, chargingRateUnit } = commandPayload
887 if (!chargingStation.hasConnector(connectorId)) {
41189456 888 logger.error(
66a7748d
JB
889 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
890 )
891 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 892 }
b3d7d654
JB
893 if (connectorId === 0) {
894 logger.error(
66a7748d
JB
895 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
896 )
897 return OCPP16Constants.OCPP_RESPONSE_REJECTED
b3d7d654 898 }
66a7748d 899 if (chargingRateUnit != null) {
bbb55ee4 900 logger.warn(
66a7748d
JB
901 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
902 )
b3d7d654 903 }
f938317f 904 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
ad490d5f 905 if (
f938317f 906 isEmptyArray(connectorStatus?.chargingProfiles) &&
a4385edc 907 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)
ad490d5f 908 ) {
66a7748d 909 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456 910 }
66a7748d 911 const currentDate = new Date()
ef9e3b33 912 const compositeScheduleInterval: Interval = {
ad490d5f 913 start: currentDate,
66a7748d
JB
914 end: addSeconds(currentDate, duration)
915 }
6fc0c6f3 916 // Get charging profiles sorted by connector id then stack level
ef9e3b33 917 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
6fc0c6f3 918 chargingStation,
66a7748d
JB
919 connectorId
920 )
921 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
922 let compositeSchedule: OCPP16ChargingSchedule | undefined
ef9e3b33 923 for (const chargingProfile of chargingProfiles) {
2466918c 924 if (chargingProfile.chargingSchedule.startSchedule == null) {
ad490d5f
JB
925 logger.debug(
926 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
ef9e3b33 927 chargingProfile.chargingProfileId
66a7748d
JB
928 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
929 )
ad490d5f 930 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
f938317f 931 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
ad490d5f 932 }
2466918c 933 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
ef9e3b33
JB
934 logger.warn(
935 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
936 chargingProfile.chargingProfileId
66a7748d
JB
937 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
938 )
ef9e3b33 939 chargingProfile.chargingSchedule.startSchedule = convertToDate(
5199f9fd 940 chargingProfile.chargingSchedule.startSchedule
3423c8a5 941 )
ef9e3b33 942 }
2466918c 943 if (chargingProfile.chargingSchedule.duration == null) {
da332e70 944 logger.debug(
ef9e3b33
JB
945 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
946 chargingProfile.chargingProfileId
66a7748d
JB
947 } has no duration defined and will be set to the maximum time allowed`
948 )
da332e70 949 // OCPP specifies that if duration is not defined, it should be infinite
ef9e3b33 950 chargingProfile.chargingSchedule.duration = differenceInSeconds(
da332e70 951 maxTime,
3423c8a5
JB
952 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
953 chargingProfile.chargingSchedule.startSchedule!
66a7748d 954 )
da332e70 955 }
0eb666db
JB
956 if (
957 !prepareChargingProfileKind(
958 connectorStatus,
ef9e3b33 959 chargingProfile,
6dde6c5f 960 compositeScheduleInterval.start,
66a7748d 961 chargingStation.logPrefix()
0eb666db
JB
962 )
963 ) {
66a7748d 964 continue
b3d7d654 965 }
41189456 966 if (
ad490d5f 967 !canProceedChargingProfile(
ef9e3b33 968 chargingProfile,
6dde6c5f 969 compositeScheduleInterval.start,
66a7748d 970 chargingStation.logPrefix()
b3d7d654 971 )
41189456 972 ) {
66a7748d 973 continue
ad490d5f 974 }
ef9e3b33
JB
975 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
976 previousCompositeSchedule,
977 chargingProfile.chargingSchedule,
66a7748d
JB
978 compositeScheduleInterval
979 )
980 previousCompositeSchedule = compositeSchedule
ef9e3b33 981 }
66a7748d 982 if (compositeSchedule != null) {
ef9e3b33
JB
983 return {
984 status: GenericStatus.Accepted,
0c1e4bc1 985 scheduleStart: compositeSchedule.startSchedule,
ef9e3b33 986 connectorId,
66a7748d
JB
987 chargingSchedule: compositeSchedule
988 }
ef9e3b33 989 }
66a7748d 990 return OCPP16Constants.OCPP_RESPONSE_REJECTED
41189456
JB
991 }
992
66a7748d 993 private handleRequestClearChargingProfile (
08f130a0 994 chargingStation: ChargingStation,
66a7748d 995 commandPayload: OCPP16ClearChargingProfileRequest
41f3983a 996 ): OCPP16ClearChargingProfileResponse {
370ae4ee 997 if (
66a7748d 998 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 999 chargingStation,
370ae4ee 1000 OCPP16SupportedFeatureProfiles.SmartCharging,
66a7748d
JB
1001 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
1002 )
370ae4ee 1003 ) {
66a7748d 1004 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
68cb8b91 1005 }
66a7748d
JB
1006 const { connectorId } = commandPayload
1007 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1008 if (!chargingStation.hasConnector(connectorId!)) {
e7aeea18 1009 logger.error(
66a7748d
JB
1010 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
1011 )
1012 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973 1013 }
66a7748d
JB
1014 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1015 const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
be9f397b 1016 if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
5dc7c990 1017 connectorStatus.chargingProfiles = []
e7aeea18 1018 logger.debug(
66a7748d
JB
1019 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
1020 )
1021 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973 1022 }
be9f397b 1023 if (connectorId == null) {
66a7748d 1024 let clearedCP = false
4334db72
JB
1025 if (chargingStation.hasEvses) {
1026 for (const evseStatus of chargingStation.evses.values()) {
f406808f 1027 for (const status of evseStatus.connectors.values()) {
73d87be1
JB
1028 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
1029 chargingStation,
1030 commandPayload,
66a7748d
JB
1031 status.chargingProfiles
1032 )
4334db72
JB
1033 }
1034 }
1035 } else {
0d1f33ba 1036 for (const id of chargingStation.connectors.keys()) {
73d87be1
JB
1037 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
1038 chargingStation,
1039 commandPayload,
66a7748d
JB
1040 chargingStation.getConnectorStatus(id)?.chargingProfiles
1041 )
c0560973
JB
1042 }
1043 }
1044 if (clearedCP) {
66a7748d 1045 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
c0560973
JB
1046 }
1047 }
66a7748d 1048 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
c0560973
JB
1049 }
1050
66a7748d 1051 private async handleRequestChangeAvailability (
08f130a0 1052 chargingStation: ChargingStation,
66a7748d 1053 commandPayload: OCPP16ChangeAvailabilityRequest
366f75f6 1054 ): Promise<OCPP16ChangeAvailabilityResponse> {
66a7748d
JB
1055 const { connectorId, type } = commandPayload
1056 if (!chargingStation.hasConnector(connectorId)) {
e7aeea18 1057 logger.error(
66a7748d
JB
1058 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
1059 )
1060 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973 1061 }
e7aeea18 1062 const chargePointStatus: OCPP16ChargePointStatus =
0d1f33ba 1063 type === OCPP16AvailabilityType.Operative
721646e9 1064 ? OCPP16ChargePointStatus.Available
66a7748d 1065 : OCPP16ChargePointStatus.Unavailable
c0560973 1066 if (connectorId === 0) {
f938317f 1067 let response: OCPP16ChangeAvailabilityResponse | undefined
ded57f02
JB
1068 if (chargingStation.hasEvses) {
1069 for (const evseStatus of chargingStation.evses.values()) {
366f75f6
JB
1070 response = await OCPP16ServiceUtils.changeAvailability(
1071 chargingStation,
225e32b0 1072 [...evseStatus.connectors.keys()],
366f75f6 1073 chargePointStatus,
66a7748d
JB
1074 type
1075 )
ded57f02 1076 }
225e32b0
JB
1077 } else {
1078 response = await OCPP16ServiceUtils.changeAvailability(
1079 chargingStation,
1080 [...chargingStation.connectors.keys()],
1081 chargePointStatus,
66a7748d
JB
1082 type
1083 )
c0560973 1084 }
66a7748d
JB
1085 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1086 return response!
e7aeea18
JB
1087 } else if (
1088 connectorId > 0 &&
66a7748d
JB
1089 (chargingStation.isChargingStationAvailable() ||
1090 (!chargingStation.isChargingStationAvailable() &&
0d1f33ba 1091 type === OCPP16AvailabilityType.Inoperative))
e7aeea18 1092 ) {
5e3cb728 1093 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
66a7748d
JB
1094 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1095 chargingStation.getConnectorStatus(connectorId)!.availability = type
1096 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
c0560973 1097 }
66a7748d
JB
1098 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1099 chargingStation.getConnectorStatus(connectorId)!.availability = type
4ecff7ce
JB
1100 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1101 chargingStation,
ef6fa3fb 1102 connectorId,
66a7748d
JB
1103 chargePointStatus
1104 )
1105 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
c0560973 1106 }
66a7748d 1107 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
c0560973
JB
1108 }
1109
66a7748d 1110 private async handleRequestRemoteStartTransaction (
08f130a0 1111 chargingStation: ChargingStation,
66a7748d 1112 commandPayload: RemoteStartTransactionRequest
f03e1042 1113 ): Promise<GenericResponse> {
66a7748d
JB
1114 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1115 if (!chargingStation.hasConnector(transactionConnectorId)) {
1116 return await this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
1117 chargingStation,
1118 transactionConnectorId,
66a7748d
JB
1119 idTag
1120 )
649287f8
JB
1121 }
1122 if (
d193a949
JB
1123 !chargingStation.isChargingStationAvailable() ||
1124 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8 1125 ) {
66a7748d 1126 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1127 chargingStation,
1128 transactionConnectorId,
66a7748d
JB
1129 idTag
1130 )
649287f8 1131 }
b10202d7 1132 // idTag authorization check required
d984c13f 1133 if (
66a7748d 1134 chargingStation.getAuthorizeRemoteTxRequests() &&
b10202d7 1135 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
66dd3447 1136 ) {
66a7748d 1137 return await this.notifyRemoteStartTransactionRejected(
08f130a0 1138 chargingStation,
e7aeea18 1139 transactionConnectorId,
66a7748d
JB
1140 idTag
1141 )
c0560973 1142 }
b10202d7
JB
1143 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1144 chargingStation,
1145 transactionConnectorId,
1146 OCPP16ChargePointStatus.Preparing
1147 )
649287f8 1148 if (
b10202d7
JB
1149 chargingProfile != null &&
1150 !this.setRemoteStartTransactionChargingProfile(
1151 chargingStation,
1152 transactionConnectorId,
1153 chargingProfile
1154 )
649287f8 1155 ) {
66a7748d 1156 return await this.notifyRemoteStartTransactionRejected(
649287f8
JB
1157 chargingStation,
1158 transactionConnectorId,
66a7748d
JB
1159 idTag
1160 )
649287f8 1161 }
54510a60 1162 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
a7fc8211
JB
1163 }
1164
66a7748d 1165 private async notifyRemoteStartTransactionRejected (
08f130a0 1166 chargingStation: ChargingStation,
e7aeea18 1167 connectorId: number,
66a7748d 1168 idTag: string
f03e1042 1169 ): Promise<GenericResponse> {
66a7748d 1170 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f406808f 1171 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
1172 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1173 chargingStation,
ef6fa3fb 1174 connectorId,
66a7748d
JB
1175 OCPP16ChargePointStatus.Available
1176 )
e060fe58 1177 }
e7aeea18 1178 logger.warn(
54510a60 1179 `${chargingStation.logPrefix()} Remote start transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
66a7748d
JB
1180 )
1181 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973
JB
1182 }
1183
66a7748d 1184 private setRemoteStartTransactionChargingProfile (
08f130a0 1185 chargingStation: ChargingStation,
e7aeea18 1186 connectorId: number,
66a7748d 1187 chargingProfile: OCPP16ChargingProfile
e7aeea18 1188 ): boolean {
5199f9fd 1189 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
66a7748d 1190 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
e7aeea18 1191 logger.debug(
944d4529 1192 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
66a7748d
JB
1193 chargingProfile
1194 )
1195 return true
a7fc8211 1196 }
f406808f
JB
1197 logger.warn(
1198 `${chargingStation.logPrefix()} Not allowed to set ${
1199 chargingProfile.chargingProfilePurpose
66a7748d
JB
1200 } charging profile(s) at remote start transaction`
1201 )
1202 return false
a7fc8211
JB
1203 }
1204
66a7748d 1205 private async handleRequestRemoteStopTransaction (
08f130a0 1206 chargingStation: ChargingStation,
66a7748d 1207 commandPayload: RemoteStopTransactionRequest
f03e1042 1208 ): Promise<GenericResponse> {
66a7748d 1209 const { transactionId } = commandPayload
ded57f02
JB
1210 if (chargingStation.hasEvses) {
1211 for (const [evseId, evseStatus] of chargingStation.evses) {
1212 if (evseId > 0) {
1213 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1214 if (connectorStatus.transactionId === transactionId) {
66a7748d 1215 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ded57f02
JB
1216 }
1217 }
1218 }
1219 }
1220 } else {
1221 for (const connectorId of chargingStation.connectors.keys()) {
1222 if (
1223 connectorId > 0 &&
1224 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1225 ) {
66a7748d 1226 return await OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
ef6fa3fb 1227 }
c0560973
JB
1228 }
1229 }
44b9b577 1230 logger.warn(
66a7748d
JB
1231 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id ${transactionId}`
1232 )
1233 return OCPP16Constants.OCPP_RESPONSE_REJECTED
c0560973 1234 }
47e22477 1235
66a7748d 1236 private handleRequestUpdateFirmware (
b03df580 1237 chargingStation: ChargingStation,
66a7748d 1238 commandPayload: OCPP16UpdateFirmwareRequest
b03df580
JB
1239 ): OCPP16UpdateFirmwareResponse {
1240 if (
66a7748d 1241 !OCPP16ServiceUtils.checkFeatureProfile(
b03df580
JB
1242 chargingStation,
1243 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1244 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1245 )
b03df580 1246 ) {
5d280aae 1247 logger.warn(
66a7748d
JB
1248 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1249 )
1250 return OCPP16Constants.OCPP_RESPONSE_EMPTY
5d280aae 1251 }
95dab6cf
JB
1252 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1253 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1254 const { retrieveDate } = commandPayload
5199f9fd 1255 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
5d280aae 1256 logger.warn(
66a7748d
JB
1257 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1258 )
1259 return OCPP16Constants.OCPP_RESPONSE_EMPTY
b03df580 1260 }
66a7748d 1261 const now = Date.now()
5199f9fd 1262 if (retrieveDate.getTime() <= now) {
66a7748d 1263 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
c9a4f9ea 1264 } else {
5199f9fd
JB
1265 setTimeout(() => {
1266 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1267 }, retrieveDate.getTime() - now)
c9a4f9ea 1268 }
66a7748d 1269 return OCPP16Constants.OCPP_RESPONSE_EMPTY
c9a4f9ea
JB
1270 }
1271
66a7748d 1272 private async updateFirmwareSimulation (
c9a4f9ea 1273 chargingStation: ChargingStation,
90293abb 1274 maxDelay = 30,
66a7748d 1275 minDelay = 15
c9a4f9ea 1276 ): Promise<void> {
66a7748d
JB
1277 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1278 return
1bf29f5b 1279 }
ded57f02
JB
1280 if (chargingStation.hasEvses) {
1281 for (const [evseId, evseStatus] of chargingStation.evses) {
1282 if (evseId > 0) {
1283 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1284 if (connectorStatus.transactionStarted === false) {
ded57f02
JB
1285 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1286 chargingStation,
1287 connectorId,
66a7748d
JB
1288 OCPP16ChargePointStatus.Unavailable
1289 )
ded57f02
JB
1290 }
1291 }
1292 }
1293 }
1294 } else {
1295 for (const connectorId of chargingStation.connectors.keys()) {
1296 if (
1297 connectorId > 0 &&
1298 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1299 ) {
1300 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1301 chargingStation,
1302 connectorId,
66a7748d
JB
1303 OCPP16ChargePointStatus.Unavailable
1304 )
ded57f02 1305 }
c9a4f9ea
JB
1306 }
1307 }
93f0c2c8 1308 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1309 OCPP16FirmwareStatusNotificationRequest,
1310 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1311 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1312 status: OCPP16FirmwareStatus.Downloading
1313 })
5199f9fd
JB
1314 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1315 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
5d280aae 1316 if (
93f0c2c8
JB
1317 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1318 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1319 ) {
66a7748d 1320 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
5d280aae 1321 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1322 OCPP16FirmwareStatusNotificationRequest,
1323 OCPP16FirmwareStatusNotificationResponse
5d280aae 1324 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1325 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1326 })
93f0c2c8 1327 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1328 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1329 return
5d280aae 1330 }
66a7748d 1331 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
c9a4f9ea 1332 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1333 OCPP16FirmwareStatusNotificationRequest,
1334 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1335 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1336 status: OCPP16FirmwareStatus.Downloaded
1337 })
5199f9fd
JB
1338 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1339 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
66a7748d
JB
1340 let wasTransactionsStarted = false
1341 let transactionsStarted: boolean
62340a29 1342 do {
66a7748d 1343 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
ded57f02 1344 if (runningTransactions > 0) {
66a7748d 1345 const waitTime = secondsToMilliseconds(15)
62340a29 1346 logger.debug(
944d4529 1347 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
66a7748d
JB
1348 waitTime
1349 )} before continuing firmware update simulation`
1350 )
1351 await sleep(waitTime)
1352 transactionsStarted = true
1353 wasTransactionsStarted = true
62340a29 1354 } else {
ded57f02
JB
1355 if (chargingStation.hasEvses) {
1356 for (const [evseId, evseStatus] of chargingStation.evses) {
1357 if (evseId > 0) {
1358 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 1359 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
ded57f02
JB
1360 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1361 chargingStation,
1362 connectorId,
66a7748d
JB
1363 OCPP16ChargePointStatus.Unavailable
1364 )
ded57f02
JB
1365 }
1366 }
1367 }
1368 }
1369 } else {
1370 for (const connectorId of chargingStation.connectors.keys()) {
1371 if (
1372 connectorId > 0 &&
1373 chargingStation.getConnectorStatus(connectorId)?.status !==
1374 OCPP16ChargePointStatus.Unavailable
1375 ) {
1376 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1377 chargingStation,
1378 connectorId,
66a7748d
JB
1379 OCPP16ChargePointStatus.Unavailable
1380 )
ded57f02 1381 }
62340a29
JB
1382 }
1383 }
66a7748d 1384 transactionsStarted = false
62340a29 1385 }
66a7748d 1386 } while (transactionsStarted)
be4c6702 1387 !wasTransactionsStarted &&
66a7748d
JB
1388 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1389 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1390 return
1bf29f5b 1391 }
c9a4f9ea 1392 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1393 OCPP16FirmwareStatusNotificationRequest,
1394 OCPP16FirmwareStatusNotificationResponse
c9a4f9ea 1395 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
66a7748d
JB
1396 status: OCPP16FirmwareStatus.Installing
1397 })
5199f9fd
JB
1398 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1399 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
93f0c2c8
JB
1400 if (
1401 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1402 OCPP16FirmwareStatus.InstallationFailed
1403 ) {
66a7748d 1404 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
93f0c2c8 1405 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1406 OCPP16FirmwareStatusNotificationRequest,
1407 OCPP16FirmwareStatusNotificationResponse
93f0c2c8 1408 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
5199f9fd 1409 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1410 })
93f0c2c8 1411 chargingStation.stationInfo.firmwareStatus =
5199f9fd 1412 chargingStation.stationInfo.firmwareUpgrade.failureStatus
66a7748d 1413 return
93f0c2c8 1414 }
15748260 1415 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
66a7748d
JB
1416 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1417 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
5d280aae 1418 }
b03df580
JB
1419 }
1420
66a7748d 1421 private async handleRequestGetDiagnostics (
08f130a0 1422 chargingStation: ChargingStation,
66a7748d 1423 commandPayload: GetDiagnosticsRequest
e7aeea18 1424 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1425 if (
66a7748d 1426 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1427 chargingStation,
370ae4ee 1428 OCPP16SupportedFeatureProfiles.FirmwareManagement,
66a7748d
JB
1429 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1430 )
68cb8b91 1431 ) {
90293abb 1432 logger.warn(
66a7748d
JB
1433 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1434 )
1435 return OCPP16Constants.OCPP_RESPONSE_EMPTY
68cb8b91 1436 }
66a7748d
JB
1437 const { location } = commandPayload
1438 const uri = new URL(location)
47e22477 1439 if (uri.protocol.startsWith('ftp:')) {
66a7748d 1440 let ftpClient: Client | undefined
47e22477 1441 try {
d972af76 1442 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
a974c8e4
JB
1443 .filter(file => file.endsWith('.log'))
1444 .map(file => join('./', file))
5199f9fd 1445 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
66a7748d
JB
1446 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1447 ftpClient = new Client()
47e22477
JB
1448 const accessResponse = await ftpClient.access({
1449 host: uri.host,
9bf0ef23
JB
1450 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1451 ...(isNotEmptyString(uri.username) && { user: uri.username }),
66a7748d
JB
1452 ...(isNotEmptyString(uri.password) && { password: uri.password })
1453 })
1454 let uploadResponse: FTPResponse | undefined
47e22477 1455 if (accessResponse.code === 220) {
a974c8e4 1456 ftpClient.trackProgress(info => {
e7aeea18 1457 logger.info(
56563a3c 1458 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1459 info.bytes / 1024
66a7748d
JB
1460 } bytes transferred from diagnostics archive ${info.name}`
1461 )
6a8329b4
JB
1462 chargingStation.ocppRequestService
1463 .requestHandler<
66a7748d
JB
1464 OCPP16DiagnosticsStatusNotificationRequest,
1465 OCPP16DiagnosticsStatusNotificationResponse
1466 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1467 status: OCPP16DiagnosticsStatus.Uploading
1468 })
a974c8e4 1469 .catch(error => {
6a8329b4 1470 logger.error(
944d4529
JB
1471 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1472 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1473 }'`,
66a7748d
JB
1474 error
1475 )
1476 })
1477 })
e7aeea18 1478 uploadResponse = await ftpClient.uploadFrom(
d972af76 1479 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
66a7748d
JB
1480 `${uri.pathname}${diagnosticsArchive}`
1481 )
47e22477 1482 if (uploadResponse.code === 226) {
08f130a0 1483 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1484 OCPP16DiagnosticsStatusNotificationRequest,
1485 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1486 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1487 status: OCPP16DiagnosticsStatus.Uploaded
1488 })
5199f9fd 1489 ftpClient.close()
66a7748d 1490 return { fileName: diagnosticsArchive }
47e22477 1491 }
e7aeea18
JB
1492 throw new OCPPError(
1493 ErrorType.GENERIC_ERROR,
5199f9fd 1494 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
66a7748d
JB
1495 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1496 )
47e22477 1497 }
e7aeea18
JB
1498 throw new OCPPError(
1499 ErrorType.GENERIC_ERROR,
5199f9fd 1500 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
66a7748d
JB
1501 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1502 )
47e22477 1503 } catch (error) {
08f130a0 1504 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1505 OCPP16DiagnosticsStatusNotificationRequest,
1506 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1507 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1508 status: OCPP16DiagnosticsStatus.UploadFailed
1509 })
5199f9fd 1510 ftpClient?.close()
66a7748d 1511 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1512 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1513 chargingStation,
e7aeea18
JB
1514 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1515 error as Error,
66a7748d
JB
1516 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1517 )!
47e22477
JB
1518 }
1519 } else {
e7aeea18 1520 logger.error(
08f130a0 1521 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1522 uri.protocol
66a7748d
JB
1523 } to transfer the diagnostic logs archive`
1524 )
08f130a0 1525 await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
1526 OCPP16DiagnosticsStatusNotificationRequest,
1527 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1528 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
66a7748d
JB
1529 status: OCPP16DiagnosticsStatus.UploadFailed
1530 })
1531 return OCPP16Constants.OCPP_RESPONSE_EMPTY
47e22477
JB
1532 }
1533 }
802cfa13 1534
66a7748d 1535 private handleRequestTriggerMessage (
08f130a0 1536 chargingStation: ChargingStation,
66a7748d 1537 commandPayload: OCPP16TriggerMessageRequest
e7aeea18 1538 ): OCPP16TriggerMessageResponse {
66a7748d 1539 const { requestedMessage, connectorId } = commandPayload
370ae4ee
JB
1540 if (
1541 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1542 chargingStation,
370ae4ee 1543 OCPP16SupportedFeatureProfiles.RemoteTrigger,
66a7748d 1544 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8 1545 ) ||
0d1f33ba 1546 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1547 ) {
66a7748d 1548 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
68cb8b91 1549 }
c60ed4b8 1550 if (
4caa7e67 1551 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1552 chargingStation,
1553 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
66a7748d
JB
1554 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1555 connectorId!
c60ed4b8
JB
1556 )
1557 ) {
66a7748d 1558 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
dc661702 1559 }
f2f33b97
JB
1560 switch (requestedMessage) {
1561 case OCPP16MessageTrigger.BootNotification:
f2f33b97 1562 case OCPP16MessageTrigger.Heartbeat:
f2f33b97 1563 case OCPP16MessageTrigger.StatusNotification:
f2f33b97
JB
1564 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1565 default:
1566 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
802cfa13
JB
1567 }
1568 }
77b95a89 1569
66a7748d 1570 private handleRequestDataTransfer (
77b95a89 1571 chargingStation: ChargingStation,
66a7748d 1572 commandPayload: OCPP16DataTransferRequest
77b95a89 1573 ): OCPP16DataTransferResponse {
66a7748d 1574 const { vendorId } = commandPayload
77b95a89 1575 try {
0d1f33ba 1576 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
66a7748d 1577 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
77b95a89 1578 }
66a7748d 1579 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
77b95a89 1580 } catch (error) {
66a7748d 1581 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1582 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1583 chargingStation,
1584 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1585 error as Error,
66a7748d
JB
1586 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1587 )!
77b95a89
JB
1588 }
1589 }
24578c31 1590
66a7748d 1591 private async handleRequestReserveNow (
24578c31 1592 chargingStation: ChargingStation,
66a7748d 1593 commandPayload: OCPP16ReserveNowRequest
24578c31 1594 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1595 if (
1596 !OCPP16ServiceUtils.checkFeatureProfile(
1597 chargingStation,
1598 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1599 OCPP16IncomingRequestCommand.RESERVE_NOW
66dd3447
JB
1600 )
1601 ) {
66a7748d 1602 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
66dd3447 1603 }
95dab6cf
JB
1604 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1605 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
66a7748d
JB
1606 const { reservationId, idTag, connectorId } = commandPayload
1607 let response: OCPP16ReserveNowResponse
24578c31 1608 try {
d984c13f 1609 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
66a7748d 1610 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1611 }
10e8c3e1 1612 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
66a7748d 1613 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1614 }
66dd3447 1615 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
66a7748d 1616 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
24578c31 1617 }
66a7748d 1618 await removeExpiredReservations(chargingStation)
f938317f 1619 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
178956d8 1620 case OCPP16ChargePointStatus.Faulted:
66a7748d
JB
1621 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1622 break
178956d8
JB
1623 case OCPP16ChargePointStatus.Preparing:
1624 case OCPP16ChargePointStatus.Charging:
1625 case OCPP16ChargePointStatus.SuspendedEV:
1626 case OCPP16ChargePointStatus.SuspendedEVSE:
1627 case OCPP16ChargePointStatus.Finishing:
66a7748d
JB
1628 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1629 break
178956d8 1630 case OCPP16ChargePointStatus.Unavailable:
66a7748d
JB
1631 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1632 break
178956d8 1633 case OCPP16ChargePointStatus.Reserved:
66dd3447 1634 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
66a7748d
JB
1635 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1636 break
24578c31
JB
1637 }
1638 // eslint-disable-next-line no-fallthrough
1639 default:
66dd3447 1640 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
66a7748d
JB
1641 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1642 break
d193a949
JB
1643 }
1644 await chargingStation.addReservation({
1645 id: commandPayload.reservationId,
66a7748d
JB
1646 ...commandPayload
1647 })
1648 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1649 break
24578c31 1650 }
66a7748d 1651 return response
24578c31 1652 } catch (error) {
66a7748d
JB
1653 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1654 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1655 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1656 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1657 chargingStation,
1658 OCPP16IncomingRequestCommand.RESERVE_NOW,
1659 error as Error,
66a7748d
JB
1660 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1661 )!
24578c31
JB
1662 }
1663 }
1664
66a7748d 1665 private async handleRequestCancelReservation (
24578c31 1666 chargingStation: ChargingStation,
66a7748d 1667 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1668 ): Promise<GenericResponse> {
66dd3447
JB
1669 if (
1670 !OCPP16ServiceUtils.checkFeatureProfile(
1671 chargingStation,
1672 OCPP16SupportedFeatureProfiles.Reservation,
66a7748d 1673 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
66dd3447
JB
1674 )
1675 ) {
66a7748d 1676 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
66dd3447 1677 }
24578c31 1678 try {
66a7748d
JB
1679 const { reservationId } = commandPayload
1680 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
300418e9 1681 if (reservation == null) {
90aceaf6 1682 logger.debug(
66a7748d
JB
1683 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1684 )
1685 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
24578c31 1686 }
ec9f36cc 1687 await chargingStation.removeReservation(
300418e9 1688 reservation,
66a7748d
JB
1689 ReservationTerminationReason.RESERVATION_CANCELED
1690 )
1691 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
24578c31 1692 } catch (error) {
66a7748d 1693 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 1694 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1695 chargingStation,
1696 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1697 error as Error,
66a7748d
JB
1698 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1699 )!
24578c31
JB
1700 }
1701 }
c0560973 1702}