1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
3 import type { ValidateFunction
} from
'ajv'
5 import { Client
, type FTPResponse
} from
'basic-ftp'
11 secondsToMilliseconds
,
13 import { maxTime
} from
'date-fns/constants'
14 import { randomInt
} from
'node:crypto'
15 import { createWriteStream
, readdirSync
} from
'node:fs'
16 import { dirname
, extname
, join
, resolve
} from
'node:path'
17 import { fileURLToPath
, URL
} from
'node:url'
18 import { create
} from
'tar'
21 canProceedChargingProfile
,
23 checkChargingStationState
,
25 getConnectorChargingProfiles
,
26 prepareChargingProfileKind
,
27 removeExpiredReservations
,
28 resetAuthorizeConnectorStatus
,
29 setConfigurationKeyValue
,
30 } from
'../../../charging-station/index.js'
31 import { OCPPError
} from
'../../../exception/index.js'
33 type ChangeConfigurationRequest
,
34 type ChangeConfigurationResponse
,
39 type GetConfigurationRequest
,
40 type GetConfigurationResponse
,
41 type GetDiagnosticsRequest
,
42 type GetDiagnosticsResponse
,
43 type IncomingRequestHandler
,
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
,
67 type OCPP16FirmwareStatusNotificationRequest
,
68 type OCPP16FirmwareStatusNotificationResponse
,
69 type OCPP16GetCompositeScheduleRequest
,
70 type OCPP16GetCompositeScheduleResponse
,
71 type OCPP16HeartbeatRequest
,
72 type OCPP16HeartbeatResponse
,
73 OCPP16IncomingRequestCommand
,
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
,
92 type RemoteStartTransactionRequest
,
93 type RemoteStopTransactionRequest
,
94 ReservationTerminationReason
,
96 type SetChargingProfileRequest
,
97 type SetChargingProfileResponse
,
98 type UnlockConnectorRequest
,
99 type UnlockConnectorResponse
,
100 } from
'../../../types/index.js'
106 formatDurationMilliSeconds
,
107 handleIncomingRequestError
,
114 } from
'../../../utils/index.js'
115 import { OCPPIncomingRequestService
} from
'../OCPPIncomingRequestService.js'
116 import { OCPP16Constants
} from
'./OCPP16Constants.js'
117 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils.js'
119 const moduleName
= 'OCPP16IncomingRequestService'
121 export class OCPP16IncomingRequestService
extends OCPPIncomingRequestService
{
122 protected payloadValidateFunctions
: Map
<OCPP16IncomingRequestCommand
, ValidateFunction
<JsonType
>>
124 private readonly incomingRequestHandlers
: Map
<
125 OCPP16IncomingRequestCommand
,
126 IncomingRequestHandler
129 public constructor () {
130 // if (new.target.name === moduleName) {
131 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
133 super(OCPPVersion
.VERSION_16
)
134 this.incomingRequestHandlers
= new Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>([
136 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
137 this.handleRequestCancelReservation
.bind(this) as unknown
as IncomingRequestHandler
,
140 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
141 this.handleRequestChangeAvailability
.bind(this) as unknown
as IncomingRequestHandler
,
144 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
145 this.handleRequestChangeConfiguration
.bind(this) as unknown
as IncomingRequestHandler
,
148 OCPP16IncomingRequestCommand
.CLEAR_CACHE
,
149 this.handleRequestClearCache
.bind(this) as IncomingRequestHandler
,
152 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
153 this.handleRequestClearChargingProfile
.bind(this) as IncomingRequestHandler
,
156 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
157 this.handleRequestDataTransfer
.bind(this) as unknown
as IncomingRequestHandler
,
160 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
,
161 this.handleRequestGetCompositeSchedule
.bind(this) as unknown
as IncomingRequestHandler
,
164 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
165 this.handleRequestGetConfiguration
.bind(this) as IncomingRequestHandler
,
168 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
169 this.handleRequestGetDiagnostics
.bind(this) as IncomingRequestHandler
,
172 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
173 this.handleRequestRemoteStartTransaction
.bind(this) as unknown
as IncomingRequestHandler
,
176 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
177 this.handleRequestRemoteStopTransaction
.bind(this) as unknown
as IncomingRequestHandler
,
180 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
181 this.handleRequestReserveNow
.bind(this) as unknown
as IncomingRequestHandler
,
184 OCPP16IncomingRequestCommand
.RESET
,
185 this.handleRequestReset
.bind(this) as unknown
as IncomingRequestHandler
,
188 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
189 this.handleRequestSetChargingProfile
.bind(this) as unknown
as IncomingRequestHandler
,
192 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
193 this.handleRequestTriggerMessage
.bind(this) as unknown
as IncomingRequestHandler
,
196 OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
,
197 this.handleRequestUnlockConnector
.bind(this) as unknown
as IncomingRequestHandler
,
200 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
201 this.handleRequestUpdateFirmware
.bind(this) as unknown
as IncomingRequestHandler
,
204 this.payloadValidateFunctions
= new Map
<
205 OCPP16IncomingRequestCommand
,
206 ValidateFunction
<JsonType
>
209 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
211 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16CancelReservationRequest
>(
212 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
219 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
221 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ChangeAvailabilityRequest
>(
222 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
229 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
231 OCPP16ServiceUtils
.parseJsonSchemaFile
<ChangeConfigurationRequest
>(
232 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
239 OCPP16IncomingRequestCommand
.CLEAR_CACHE
,
241 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ClearCacheRequest
>(
242 'assets/json-schemas/ocpp/1.6/ClearCache.json',
249 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
251 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ClearChargingProfileRequest
>(
252 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
259 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
261 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16DataTransferRequest
>(
262 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
269 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
,
271 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16GetCompositeScheduleRequest
>(
272 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
279 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
281 OCPP16ServiceUtils
.parseJsonSchemaFile
<GetConfigurationRequest
>(
282 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
289 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
291 OCPP16ServiceUtils
.parseJsonSchemaFile
<GetDiagnosticsRequest
>(
292 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
299 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
301 OCPP16ServiceUtils
.parseJsonSchemaFile
<RemoteStartTransactionRequest
>(
302 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
309 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
311 OCPP16ServiceUtils
.parseJsonSchemaFile
<RemoteStopTransactionRequest
>(
312 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
319 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
321 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ReserveNowRequest
>(
322 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
329 OCPP16IncomingRequestCommand
.RESET
,
331 OCPP16ServiceUtils
.parseJsonSchemaFile
<ResetRequest
>(
332 'assets/json-schemas/ocpp/1.6/Reset.json',
339 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
341 OCPP16ServiceUtils
.parseJsonSchemaFile
<SetChargingProfileRequest
>(
342 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
349 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
351 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16TriggerMessageRequest
>(
352 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
359 OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
,
361 OCPP16ServiceUtils
.parseJsonSchemaFile
<UnlockConnectorRequest
>(
362 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
369 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
371 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16UpdateFirmwareRequest
>(
372 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
379 // Handle incoming request events
381 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
383 chargingStation
: ChargingStation
,
384 request
: RemoteStartTransactionRequest
,
385 response
: GenericResponse
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
>(
394 OCPP16RequestCommand
.START_TRANSACTION
,
401 if (response
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
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}'`
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}'`
420 .catch((error
: unknown
) => {
422 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
430 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
432 chargingStation
: ChargingStation
,
433 request
: RemoteStopTransactionRequest
,
434 response
: GenericResponse
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
)
442 if (response
.status === GenericStatus
.Accepted
) {
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()}'`
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()}'`
459 .catch((error
: unknown
) => {
461 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote stop transaction error:`,
469 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
471 chargingStation
: ChargingStation
,
472 request
: OCPP16TriggerMessageRequest
,
473 response
: OCPP16TriggerMessageResponse
475 if (response
.status !== OCPP16TriggerMessageStatus
.ACCEPTED
) {
478 const { connectorId
, requestedMessage
} = request
479 const errorHandler
= (error
: unknown
): void => {
481 `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
485 switch (requestedMessage
) {
486 case OCPP16MessageTrigger
.BootNotification
:
487 chargingStation
.ocppRequestService
489 OCPP16BootNotificationRequest
,
490 OCPP16BootNotificationResponse
491 >(chargingStation
, OCPP16RequestCommand
.BOOT_NOTIFICATION
, chargingStation
.bootNotificationRequest
as OCPP16BootNotificationRequest
, { skipBufferingOnError
: true, triggerMessage
: true })
494 case OCPP16MessageTrigger
.Heartbeat
:
495 chargingStation
.ocppRequestService
496 .requestHandler
<OCPP16HeartbeatRequest
, OCPP16HeartbeatResponse
>(
498 OCPP16RequestCommand
.HEARTBEAT
,
501 triggerMessage
: true,
506 case OCPP16MessageTrigger
.StatusNotification
:
507 if (connectorId
!= null) {
508 chargingStation
.ocppRequestService
509 .requestHandler
<OCPP16StatusNotificationRequest
, OCPP16StatusNotificationResponse
>(
511 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
514 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
515 status: chargingStation
.getConnectorStatus(connectorId
)
516 ?.status as OCPP16ChargePointStatus
,
519 triggerMessage
: true,
523 } else if (chargingStation
.hasEvses
) {
524 for (const evseStatus
of chargingStation
.evses
.values()) {
525 for (const [id
, connectorStatus
] of evseStatus
.connectors
) {
526 chargingStation
.ocppRequestService
528 OCPP16StatusNotificationRequest
,
529 OCPP16StatusNotificationResponse
532 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
535 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
536 status: connectorStatus
.status as OCPP16ChargePointStatus
,
539 triggerMessage
: true,
546 for (const [id
, connectorStatus
] of chargingStation
.connectors
) {
547 chargingStation
.ocppRequestService
549 OCPP16StatusNotificationRequest
,
550 OCPP16StatusNotificationResponse
553 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
556 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
557 status: connectorStatus
.status as OCPP16ChargePointStatus
,
560 triggerMessage
: true,
570 this.validatePayload
= this.validatePayload
.bind(this)
573 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
574 public async incomingRequestHandler
<ReqType
extends JsonType
, ResType
extends JsonType
>(
575 chargingStation
: ChargingStation
,
577 commandName
: OCPP16IncomingRequestCommand
,
578 commandPayload
: ReqType
580 let response
: ResType
582 chargingStation
.stationInfo
?.ocppStrictCompliance
=== true &&
583 chargingStation
.inPendingState() &&
584 (commandName
=== OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
||
585 commandName
=== OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
)
588 ErrorType
.SECURITY_ERROR
,
589 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
593 )} while the charging station is in pending state on the central server`,
599 chargingStation
.inAcceptedState() ||
600 chargingStation
.inPendingState() ||
601 (chargingStation
.stationInfo
?.ocppStrictCompliance
=== false &&
602 chargingStation
.inUnknownState())
605 this.incomingRequestHandlers
.has(commandName
) &&
606 OCPP16ServiceUtils
.isIncomingRequestCommandSupported(chargingStation
, commandName
)
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
616 response
= incomingRequestHandler(chargingStation
, commandPayload
) as ResType
621 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
629 ErrorType
.NOT_IMPLEMENTED
,
630 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
641 ErrorType
.SECURITY_ERROR
,
642 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
646 )} while the charging station is not registered on the central server`,
651 // Send the built response
652 await chargingStation
.ocppRequestService
.sendResponse(
658 // Emit command name event to allow delayed handling
659 this.emit(commandName
, chargingStation
, commandPayload
, response
)
662 private async handleRequestCancelReservation (
663 chargingStation
: ChargingStation
,
664 commandPayload
: OCPP16CancelReservationRequest
665 ): Promise
<GenericResponse
> {
667 !OCPP16ServiceUtils
.checkFeatureProfile(
669 OCPP16SupportedFeatureProfiles
.Reservation
,
670 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
673 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
676 const { reservationId
} = commandPayload
677 const reservation
= chargingStation
.getReservationBy('reservationId', reservationId
)
678 if (reservation
== null) {
680 `${chargingStation.logPrefix()} Reservation with id ${reservationId.toString()} does not exist on charging station`
682 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
684 await chargingStation
.removeReservation(
686 ReservationTerminationReason
.RESERVATION_CANCELED
688 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
690 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
691 return handleIncomingRequestError
<GenericResponse
>(
693 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
696 errorResponse
: OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
,
702 private async handleRequestChangeAvailability (
703 chargingStation
: ChargingStation
,
704 commandPayload
: OCPP16ChangeAvailabilityRequest
705 ): Promise
<OCPP16ChangeAvailabilityResponse
> {
706 const { connectorId
, type } = commandPayload
707 if (!chargingStation
.hasConnector(connectorId
)) {
709 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId.toString()}`
711 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
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(
723 [...evseStatus
.connectors
.keys()],
729 response
= await OCPP16ServiceUtils
.changeAvailability(
731 [...chargingStation
.connectors
.keys()],
736 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
740 (chargingStation
.isChargingStationAvailable() ||
741 (!chargingStation
.isChargingStationAvailable() &&
742 type === OCPP16AvailabilityType
.Inoperative
))
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
749 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
750 chargingStation
.getConnectorStatus(connectorId
)!.availability
= type
751 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
756 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
758 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
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)
775 let triggerHeartbeatRestart
= false
777 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
778 OCPP16StandardParametersKey
.HeartBeatInterval
&&
781 setConfigurationKeyValue(
783 OCPP16StandardParametersKey
.HeartbeatInterval
,
786 triggerHeartbeatRestart
= true
789 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
790 OCPP16StandardParametersKey
.HeartbeatInterval
&&
793 setConfigurationKeyValue(
795 OCPP16StandardParametersKey
.HeartBeatInterval
,
798 triggerHeartbeatRestart
= true
800 if (triggerHeartbeatRestart
) {
801 chargingStation
.restartHeartbeat()
804 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
805 OCPP16StandardParametersKey
.WebSocketPingInterval
&&
808 chargingStation
.restartWebSocketPing()
811 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
812 OCPP16StandardParametersKey
.MeterValueSampleInterval
&&
813 chargingStation
.getNumberOfRunningTransactions() > 0 &&
818 connectorId
<= chargingStation
.getNumberOfConnectors();
821 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
822 chargingStation
.restartMeterValues(
824 secondsToMilliseconds(convertToInt(value
))
829 if (keyToChange
.reboot
=== true) {
830 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
832 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
834 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
837 private handleRequestClearChargingProfile (
838 chargingStation
: ChargingStation
,
839 commandPayload
: OCPP16ClearChargingProfileRequest
840 ): OCPP16ClearChargingProfileResponse
{
842 !OCPP16ServiceUtils
.checkFeatureProfile(
844 OCPP16SupportedFeatureProfiles
.SmartCharging
,
845 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
848 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
850 const { connectorId
} = commandPayload
851 if (connectorId
!= null) {
852 if (!chargingStation
.hasConnector(connectorId
)) {
854 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId.toString()}`
856 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
858 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
859 if (isNotEmptyArray(connectorStatus
?.chargingProfiles
)) {
860 connectorStatus
.chargingProfiles
= []
862 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId.toString()}`
864 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
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(
874 status.chargingProfiles
876 if (clearedConnectorCP
&& !clearedCP
) {
882 for (const id
of chargingStation
.connectors
.keys()) {
883 const clearedConnectorCP
= OCPP16ServiceUtils
.clearChargingProfiles(
886 chargingStation
.getConnectorStatus(id
)?.chargingProfiles
888 if (clearedConnectorCP
&& !clearedCP
) {
894 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
897 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
900 private handleRequestDataTransfer (
901 chargingStation
: ChargingStation
,
902 commandPayload
: OCPP16DataTransferRequest
903 ): OCPP16DataTransferResponse
{
904 const { vendorId
} = commandPayload
906 if (vendorId
=== chargingStation
.stationInfo
?.chargePointVendor
) {
907 return OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
909 return OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
911 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
912 return handleIncomingRequestError
<OCPP16DataTransferResponse
>(
914 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
916 { errorResponse
: OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_REJECTED
}
921 private handleRequestGetCompositeSchedule (
922 chargingStation
: ChargingStation
,
923 commandPayload
: OCPP16GetCompositeScheduleRequest
924 ): OCPP16GetCompositeScheduleResponse
{
926 !OCPP16ServiceUtils
.checkFeatureProfile(
928 OCPP16SupportedFeatureProfiles
.SmartCharging
,
929 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
932 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
934 const { chargingRateUnit
, connectorId
, duration
} = commandPayload
935 if (!chargingStation
.hasConnector(connectorId
)) {
937 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId.toString()}`
939 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
941 if (connectorId
=== 0) {
943 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId.toString()} is not yet supported`
945 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
947 if (chargingRateUnit
!= null) {
949 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
952 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
954 isEmpty(connectorStatus
?.chargingProfiles
) &&
955 isEmpty(chargingStation
.getConnectorStatus(0)?.chargingProfiles
)
957 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
959 const currentDate
= new Date()
960 const compositeScheduleInterval
: Interval
= {
961 end
: addSeconds(currentDate
, duration
),
964 // FIXME: add and handle charging station charging profiles
965 const chargingProfiles
: OCPP16ChargingProfile
[] = getConnectorChargingProfiles(
969 let previousCompositeSchedule
: OCPP16ChargingSchedule
| undefined
970 let compositeSchedule
: OCPP16ChargingSchedule
| undefined
971 for (const chargingProfile
of chargingProfiles
) {
972 if (chargingProfile
.chargingSchedule
.startSchedule
== null) {
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`
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
979 if (!isDate(chargingProfile
.chargingSchedule
.startSchedule
)) {
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`
983 chargingProfile
.chargingSchedule
.startSchedule
= convertToDate(
984 chargingProfile
.chargingSchedule
.startSchedule
987 if (chargingProfile
.chargingSchedule
.duration
== null) {
989 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no duration defined and will be set to the maximum time allowed`
991 // OCPP specifies that if duration is not defined, it should be infinite
992 chargingProfile
.chargingSchedule
.duration
= differenceInSeconds(
994 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
995 chargingProfile
.chargingSchedule
.startSchedule
!
999 !prepareChargingProfileKind(
1002 compositeScheduleInterval
.start
,
1003 chargingStation
.logPrefix()
1009 !canProceedChargingProfile(
1011 compositeScheduleInterval
.start
,
1012 chargingStation
.logPrefix()
1017 compositeSchedule
= OCPP16ServiceUtils
.composeChargingSchedules(
1018 previousCompositeSchedule
,
1019 chargingProfile
.chargingSchedule
,
1020 compositeScheduleInterval
1022 previousCompositeSchedule
= compositeSchedule
1024 if (compositeSchedule
!= null) {
1026 chargingSchedule
: compositeSchedule
,
1028 scheduleStart
: compositeSchedule
.startSchedule
,
1029 status: GenericStatus
.Accepted
,
1032 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
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
)) {
1048 configurationKey
.push({
1050 readonly: configKey
.readonly,
1051 value
: configKey
.value
,
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
)) {
1061 configurationKey
.push({
1063 readonly: keyFound
.readonly,
1064 value
: keyFound
.value
,
1077 private async handleRequestGetDiagnostics (
1078 chargingStation
: ChargingStation
,
1079 commandPayload
: GetDiagnosticsRequest
1080 ): Promise
<GetDiagnosticsResponse
> {
1082 !OCPP16ServiceUtils
.checkFeatureProfile(
1084 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
1085 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
1089 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1091 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
1093 const { location
} = commandPayload
1094 const uri
= new URL(location
)
1095 if (uri
.protocol
.startsWith('ftp:')) {
1096 let ftpClient
: Client
| undefined
1098 const logConfiguration
= Configuration
.getConfigurationSection
<LogConfiguration
>(
1099 ConfigurationSection
.log
1101 const logFiles
= readdirSync(
1102 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1103 resolve((fileURLToPath(import.meta
.url
), '../', dirname(logConfiguration
.file
!)))
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({
1115 ...(isNotEmptyString(uri
.port
) && { port
: convertToInt(uri
.port
) }),
1116 ...(isNotEmptyString(uri
.username
) && { user
: uri
.username
}),
1117 ...(isNotEmptyString(uri
.password
) && { password
: uri
.password
}),
1119 let uploadResponse
: FTPResponse
| undefined
1120 if (accessResponse
.code
=== 220) {
1121 ftpClient
.trackProgress(info
=> {
1123 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${(
1125 ).toString()} bytes transferred from diagnostics archive ${info.name}`
1127 chargingStation
.ocppRequestService
1129 OCPP16DiagnosticsStatusNotificationRequest
,
1130 OCPP16DiagnosticsStatusNotificationResponse
1131 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1132 status: OCPP16DiagnosticsStatus
.Uploading
,
1134 .catch((error
: unknown
) => {
1136 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1137 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1143 uploadResponse
= await ftpClient
.uploadFrom(
1144 join(resolve(dirname(fileURLToPath(import.meta
.url
)), '../'), diagnosticsArchive
),
1145 `${uri.pathname}${diagnosticsArchive}`
1147 if (uploadResponse
.code
=== 226) {
1148 await chargingStation
.ocppRequestService
.requestHandler
<
1149 OCPP16DiagnosticsStatusNotificationRequest
,
1150 OCPP16DiagnosticsStatusNotificationResponse
1151 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1152 status: OCPP16DiagnosticsStatus
.Uploaded
,
1155 return { fileName
: diagnosticsArchive
}
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
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
1170 await chargingStation
.ocppRequestService
.requestHandler
<
1171 OCPP16DiagnosticsStatusNotificationRequest
,
1172 OCPP16DiagnosticsStatusNotificationResponse
1173 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1174 status: OCPP16DiagnosticsStatus
.UploadFailed
,
1177 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1178 return handleIncomingRequestError
<GetDiagnosticsResponse
>(
1180 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1182 { errorResponse
: OCPP16Constants
.OCPP_RESPONSE_EMPTY
}
1187 `${chargingStation.logPrefix()} Unsupported protocol ${
1189 } to transfer the diagnostic logs archive`
1191 await chargingStation
.ocppRequestService
.requestHandler
<
1192 OCPP16DiagnosticsStatusNotificationRequest
,
1193 OCPP16DiagnosticsStatusNotificationResponse
1194 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1195 status: OCPP16DiagnosticsStatus
.UploadFailed
,
1197 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
1201 private async handleRequestRemoteStartTransaction (
1202 chargingStation
: ChargingStation
,
1203 commandPayload
: RemoteStartTransactionRequest
1204 ): Promise
<GenericResponse
> {
1205 if (commandPayload
.connectorId
== null) {
1207 let connectorId
= 1;
1208 connectorId
<= chargingStation
.getNumberOfConnectors();
1212 chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== false &&
1213 !OCPP16ServiceUtils
.hasReservation(chargingStation
, connectorId
, commandPayload
.idTag
)
1215 commandPayload
.connectorId
= connectorId
1219 if (commandPayload
.connectorId
== null) {
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`
1226 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
1229 const { chargingProfile
, connectorId
: transactionConnectorId
, idTag
} = commandPayload
1230 if (!chargingStation
.hasConnector(transactionConnectorId
)) {
1231 return this.notifyRemoteStartTransactionRejected(
1233 transactionConnectorId
,
1238 !chargingStation
.isChargingStationAvailable() ||
1239 !chargingStation
.isConnectorAvailable(transactionConnectorId
)
1241 return this.notifyRemoteStartTransactionRejected(
1243 transactionConnectorId
,
1247 // idTag authorization check required
1249 chargingStation
.getAuthorizeRemoteTxRequests() &&
1250 !(await OCPP16ServiceUtils
.isIdTagAuthorized(chargingStation
, transactionConnectorId
, idTag
))
1252 return this.notifyRemoteStartTransactionRejected(
1254 transactionConnectorId
,
1259 chargingProfile
!= null &&
1260 !this.setRemoteStartTransactionChargingProfile(
1262 transactionConnectorId
,
1266 return this.notifyRemoteStartTransactionRejected(
1268 transactionConnectorId
,
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}'`
1278 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
1281 private handleRequestRemoteStopTransaction (
1282 chargingStation
: ChargingStation
,
1283 commandPayload
: RemoteStopTransactionRequest
1284 ): GenericResponse
{
1285 const { transactionId
} = commandPayload
1286 if (chargingStation
.getConnectorIdByTransactionId(transactionId
) != null) {
1288 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId.toString()}'`
1290 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
1293 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId.toString()}'`
1295 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
1298 private async handleRequestReserveNow (
1299 chargingStation
: ChargingStation
,
1300 commandPayload
: OCPP16ReserveNowRequest
1301 ): Promise
<OCPP16ReserveNowResponse
> {
1303 !OCPP16ServiceUtils
.checkFeatureProfile(
1305 OCPP16SupportedFeatureProfiles
.Reservation
,
1306 OCPP16IncomingRequestCommand
.RESERVE_NOW
1309 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
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
)) {
1316 `${chargingStation.logPrefix()} Trying to reserve a non existing connector id ${connectorId.toString()}`
1318 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
1320 if (connectorId
> 0 && !chargingStation
.isConnectorAvailable(connectorId
)) {
1321 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
1323 if (connectorId
=== 0 && !chargingStation
.getReserveConnectorZeroSupported()) {
1324 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
1326 if (!(await OCPP16ServiceUtils
.isIdTagAuthorized(chargingStation
, connectorId
, idTag
))) {
1327 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
1329 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1330 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
1331 resetAuthorizeConnectorStatus(connectorStatus
)
1332 let response
: OCPP16ReserveNowResponse
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
1343 case OCPP16ChargePointStatus
.Faulted
:
1344 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_FAULTED
1346 case OCPP16ChargePointStatus
.Unavailable
:
1347 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1349 case OCPP16ChargePointStatus
.Reserved
:
1350 if (!chargingStation
.isConnectorReservable(reservationId
, idTag
, connectorId
)) {
1351 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
1354 // eslint-disable-next-line no-fallthrough
1356 if (!chargingStation
.isConnectorReservable(reservationId
, idTag
)) {
1357 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
1360 await chargingStation
.addReservation({
1361 id
: commandPayload
.reservationId
,
1364 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_ACCEPTED
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
>(
1374 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
1376 { errorResponse
: OCPP16Constants
.OCPP_RESERVATION_RESPONSE_FAULTED
}
1381 // Simulate charging station restart
1382 private handleRequestReset (
1383 chargingStation
: ChargingStation
,
1384 commandPayload
: ResetRequest
1385 ): GenericResponse
{
1386 const { type } = commandPayload
1388 .reset(`${type}Reset` as OCPP16StopTransactionReason
)
1389 .catch(Constants
.EMPTY_FUNCTION
)
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!
1396 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
1399 private handleRequestSetChargingProfile (
1400 chargingStation
: ChargingStation
,
1401 commandPayload
: SetChargingProfileRequest
1402 ): SetChargingProfileResponse
{
1404 !OCPP16ServiceUtils
.checkFeatureProfile(
1406 OCPP16SupportedFeatureProfiles
.SmartCharging
,
1407 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
1410 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
1412 const { connectorId
, csChargingProfiles
} = commandPayload
1413 if (!chargingStation
.hasConnector(connectorId
)) {
1415 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId.toString()}`
1417 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
1420 csChargingProfiles
.chargingProfilePurpose
===
1421 OCPP16ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
1424 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
1427 csChargingProfiles
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
&&
1431 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId.toString()}`
1433 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
1435 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
1437 csChargingProfiles
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
&&
1439 connectorStatus
?.transactionStarted
=== false
1442 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId.toString()} without a started transaction`
1444 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
1447 csChargingProfiles
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
&&
1449 connectorStatus
?.transactionStarted
=== true &&
1450 csChargingProfiles
.transactionId
!= null &&
1451 csChargingProfiles
.transactionId
!== connectorStatus
.transactionId
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()}`
1459 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
1461 OCPP16ServiceUtils
.setChargingProfile(chargingStation
, connectorId
, csChargingProfiles
)
1463 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId.toString()}: %j`,
1466 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
1469 private handleRequestTriggerMessage (
1470 chargingStation
: ChargingStation
,
1471 commandPayload
: OCPP16TriggerMessageRequest
1472 ): OCPP16TriggerMessageResponse
{
1473 const { connectorId
, requestedMessage
} = commandPayload
1475 !OCPP16ServiceUtils
.checkFeatureProfile(
1477 OCPP16SupportedFeatureProfiles
.RemoteTrigger
,
1478 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
1480 !OCPP16ServiceUtils
.isMessageTriggerSupported(chargingStation
, requestedMessage
)
1482 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1485 !OCPP16ServiceUtils
.isConnectorIdValid(
1487 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1488 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1492 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1494 switch (requestedMessage
) {
1495 case OCPP16MessageTrigger
.BootNotification
:
1496 case OCPP16MessageTrigger
.Heartbeat
:
1497 case OCPP16MessageTrigger
.StatusNotification
:
1498 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1500 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1504 private async handleRequestUnlockConnector (
1505 chargingStation
: ChargingStation
,
1506 commandPayload
: UnlockConnectorRequest
1507 ): Promise
<UnlockConnectorResponse
> {
1508 const { connectorId
} = commandPayload
1509 if (!chargingStation
.hasConnector(connectorId
)) {
1511 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId.toString()}`
1513 return OCPP16Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
1515 if (connectorId
=== 0) {
1517 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`
1519 return OCPP16Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
1521 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
1522 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
1524 OCPP16StopTransactionReason
.UNLOCK_COMMAND
1526 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
1527 return OCPP16Constants
.OCPP_RESPONSE_UNLOCKED
1529 return OCPP16Constants
.OCPP_RESPONSE_UNLOCK_FAILED
1531 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1534 OCPP16ChargePointStatus
.Available
1536 return OCPP16Constants
.OCPP_RESPONSE_UNLOCKED
1539 private handleRequestUpdateFirmware (
1540 chargingStation
: ChargingStation
,
1541 commandPayload
: OCPP16UpdateFirmwareRequest
1542 ): OCPP16UpdateFirmwareResponse
{
1544 !OCPP16ServiceUtils
.checkFeatureProfile(
1546 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
1547 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
1551 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1553 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
1555 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1556 commandPayload
.retrieveDate
= convertToDate(commandPayload
.retrieveDate
)!
1557 const { retrieveDate
} = commandPayload
1559 chargingStation
.stationInfo
?.firmwareStatus
!= null &&
1560 chargingStation
.stationInfo
.firmwareStatus
!== OCPP16FirmwareStatus
.Installed
1563 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1565 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
1567 const now
= Date.now()
1568 if (retrieveDate
.getTime() <= now
) {
1569 this.updateFirmwareSimulation(chargingStation
).catch(Constants
.EMPTY_FUNCTION
)
1572 this.updateFirmwareSimulation(chargingStation
).catch(Constants
.EMPTY_FUNCTION
)
1573 }, retrieveDate
.getTime() - now
)
1575 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
1578 private notifyRemoteStartTransactionRejected (
1579 chargingStation
: ChargingStation
,
1580 connectorId
: number,
1582 ): GenericResponse
{
1583 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
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}'`
1594 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
1597 private setRemoteStartTransactionChargingProfile (
1598 chargingStation
: ChargingStation
,
1599 connectorId
: number,
1600 chargingProfile
: OCPP16ChargingProfile
1603 chargingProfile
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
&&
1604 chargingProfile
.transactionId
== null
1606 OCPP16ServiceUtils
.setChargingProfile(chargingStation
, connectorId
, chargingProfile
)
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()}`,
1617 `${chargingStation.logPrefix()} Not allowed to set ${
1618 chargingProfile.chargingProfilePurpose
1619 } charging profile(s)${chargingProfile.transactionId != null ? ' with transactionId set' : ''} at remote start transaction`
1624 private async updateFirmwareSimulation (
1625 chargingStation
: ChargingStation
,
1629 if (!checkChargingStationState(chargingStation
, chargingStation
.logPrefix())) {
1632 if (chargingStation
.hasEvses
) {
1633 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1635 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1636 if (connectorStatus
.transactionStarted
=== false) {
1637 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1640 OCPP16ChargePointStatus
.Unavailable
1647 for (const connectorId
of chargingStation
.connectors
.keys()) {
1650 chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== false
1652 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1655 OCPP16ChargePointStatus
.Unavailable
1660 await chargingStation
.ocppRequestService
.requestHandler
<
1661 OCPP16FirmwareStatusNotificationRequest
,
1662 OCPP16FirmwareStatusNotificationResponse
1663 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1664 status: OCPP16FirmwareStatus
.Downloading
,
1666 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1667 chargingStation
.stationInfo
!.firmwareStatus
= OCPP16FirmwareStatus
.Downloading
1669 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
===
1670 OCPP16FirmwareStatus
.DownloadFailed
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
,
1679 chargingStation
.stationInfo
.firmwareStatus
=
1680 chargingStation
.stationInfo
.firmwareUpgrade
.failureStatus
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
,
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
1695 const runningTransactions
= chargingStation
.getNumberOfRunningTransactions()
1696 if (runningTransactions
> 0) {
1697 const waitTime
= secondsToMilliseconds(15)
1699 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions.toString()} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1701 )} before continuing firmware update simulation`
1703 await sleep(waitTime
)
1704 transactionsStarted
= true
1705 wasTransactionsStarted
= true
1707 if (chargingStation
.hasEvses
) {
1708 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1710 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1711 if (connectorStatus
.status !== OCPP16ChargePointStatus
.Unavailable
) {
1712 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1715 OCPP16ChargePointStatus
.Unavailable
1722 for (const connectorId
of chargingStation
.connectors
.keys()) {
1725 chargingStation
.getConnectorStatus(connectorId
)?.status !==
1726 OCPP16ChargePointStatus
.Unavailable
1728 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1731 OCPP16ChargePointStatus
.Unavailable
1736 transactionsStarted
= false
1738 } while (transactionsStarted
)
1739 !wasTransactionsStarted
&& (await sleep(secondsToMilliseconds(randomInt(minDelay
, maxDelay
))))
1740 if (!checkChargingStationState(chargingStation
, chargingStation
.logPrefix())) {
1743 await chargingStation
.ocppRequestService
.requestHandler
<
1744 OCPP16FirmwareStatusNotificationRequest
,
1745 OCPP16FirmwareStatusNotificationResponse
1746 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1747 status: OCPP16FirmwareStatus
.Installing
,
1749 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1750 chargingStation
.stationInfo
!.firmwareStatus
= OCPP16FirmwareStatus
.Installing
1752 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
===
1753 OCPP16FirmwareStatus
.InstallationFailed
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
,
1762 chargingStation
.stationInfo
.firmwareStatus
=
1763 chargingStation
.stationInfo
.firmwareUpgrade
.failureStatus
1766 if (chargingStation
.stationInfo
?.firmwareUpgrade
?.reset
=== true) {
1767 await sleep(secondsToMilliseconds(randomInt(minDelay
, maxDelay
)))
1768 await chargingStation
.reset(OCPP16StopTransactionReason
.REBOOT
)
1772 private validatePayload (
1773 chargingStation
: ChargingStation
,
1774 commandName
: OCPP16IncomingRequestCommand
,
1775 commandPayload
: JsonType
1777 if (this.payloadValidateFunctions
.has(commandName
)) {
1778 return this.validateIncomingRequestPayload(chargingStation
, commandName
, commandPayload
)
1781 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`