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