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