1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import { createWriteStream
, readdirSync
} from
'node:fs';
4 import { dirname
, join
, resolve
} from
'node:path';
5 import { URL
, fileURLToPath
} from
'node:url';
7 import type { JSONSchemaType
} from
'ajv';
8 import { Client
, type FTPResponse
} from
'basic-ftp';
20 secondsToMilliseconds
,
22 import { create
} from
'tar';
24 import { OCPP16Constants
} from
'./OCPP16Constants';
25 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
28 canProceedChargingProfile
,
31 getConnectorChargingProfiles
,
32 prepareChargingProfileKind
,
33 removeExpiredReservations
,
34 setConfigurationKeyValue
,
35 } from
'../../../charging-station';
36 import { OCPPError
} from
'../../../exception';
38 type ChangeConfigurationRequest
,
39 type ChangeConfigurationResponse
,
40 type ClearChargingProfileRequest
,
41 type ClearChargingProfileResponse
,
45 type GetConfigurationRequest
,
46 type GetConfigurationResponse
,
47 type GetDiagnosticsRequest
,
48 type GetDiagnosticsResponse
,
49 type IncomingRequestHandler
,
52 OCPP16AuthorizationStatus
,
53 OCPP16AvailabilityType
,
54 type OCPP16BootNotificationRequest
,
55 type OCPP16BootNotificationResponse
,
56 type OCPP16CancelReservationRequest
,
57 type OCPP16ChangeAvailabilityRequest
,
58 type OCPP16ChangeAvailabilityResponse
,
59 OCPP16ChargePointErrorCode
,
60 OCPP16ChargePointStatus
,
61 type OCPP16ChargingProfile
,
62 OCPP16ChargingProfilePurposeType
,
63 OCPP16ChargingRateUnitType
,
64 type OCPP16ChargingSchedule
,
65 type OCPP16ChargingSchedulePeriod
,
66 type OCPP16ClearCacheRequest
,
67 type OCPP16DataTransferRequest
,
68 type OCPP16DataTransferResponse
,
69 OCPP16DataTransferVendorId
,
70 OCPP16DiagnosticsStatus
,
71 type OCPP16DiagnosticsStatusNotificationRequest
,
72 type OCPP16DiagnosticsStatusNotificationResponse
,
74 type OCPP16FirmwareStatusNotificationRequest
,
75 type OCPP16FirmwareStatusNotificationResponse
,
76 type OCPP16GetCompositeScheduleRequest
,
77 type OCPP16GetCompositeScheduleResponse
,
78 type OCPP16HeartbeatRequest
,
79 type OCPP16HeartbeatResponse
,
80 OCPP16IncomingRequestCommand
,
83 type OCPP16ReserveNowRequest
,
84 type OCPP16ReserveNowResponse
,
85 OCPP16StandardParametersKey
,
86 type OCPP16StartTransactionRequest
,
87 type OCPP16StartTransactionResponse
,
88 type OCPP16StatusNotificationRequest
,
89 type OCPP16StatusNotificationResponse
,
90 OCPP16StopTransactionReason
,
91 OCPP16SupportedFeatureProfiles
,
92 type OCPP16TriggerMessageRequest
,
93 type OCPP16TriggerMessageResponse
,
94 type OCPP16UpdateFirmwareRequest
,
95 type OCPP16UpdateFirmwareResponse
,
96 type OCPPConfigurationKey
,
98 type RemoteStartTransactionRequest
,
99 type RemoteStopTransactionRequest
,
100 ReservationTerminationReason
,
102 type SetChargingProfileRequest
,
103 type SetChargingProfileResponse
,
104 type UnlockConnectorRequest
,
105 type UnlockConnectorResponse
,
106 } from
'../../../types';
111 formatDurationMilliSeconds
,
121 } from
'../../../utils';
122 import { OCPPIncomingRequestService
} from
'../OCPPIncomingRequestService';
124 const moduleName
= 'OCPP16IncomingRequestService';
126 export class OCPP16IncomingRequestService
extends OCPPIncomingRequestService
{
127 protected jsonSchemas
: Map
<OCPP16IncomingRequestCommand
, JSONSchemaType
<JsonObject
>>;
128 private incomingRequestHandlers
: Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>;
130 public constructor() {
131 // if (new.target?.name === moduleName) {
132 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
134 super(OCPPVersion
.VERSION_16
);
135 this.incomingRequestHandlers
= new Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>([
137 OCPP16IncomingRequestCommand
.RESET
,
138 this.handleRequestReset
.bind(this) as unknown
as IncomingRequestHandler
,
141 OCPP16IncomingRequestCommand
.CLEAR_CACHE
,
142 this.handleRequestClearCache
.bind(this) as IncomingRequestHandler
,
145 OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
,
146 this.handleRequestUnlockConnector
.bind(this) as unknown
as IncomingRequestHandler
,
149 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
150 this.handleRequestGetConfiguration
.bind(this) as IncomingRequestHandler
,
153 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
154 this.handleRequestChangeConfiguration
.bind(this) as unknown
as IncomingRequestHandler
,
157 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
,
158 this.handleRequestGetCompositeSchedule
.bind(this) as unknown
as IncomingRequestHandler
,
161 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
162 this.handleRequestSetChargingProfile
.bind(this) as unknown
as IncomingRequestHandler
,
165 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
166 this.handleRequestClearChargingProfile
.bind(this) as IncomingRequestHandler
,
169 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
170 this.handleRequestChangeAvailability
.bind(this) as unknown
as IncomingRequestHandler
,
173 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
174 this.handleRequestRemoteStartTransaction
.bind(this) as unknown
as IncomingRequestHandler
,
177 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
178 this.handleRequestRemoteStopTransaction
.bind(this) as unknown
as IncomingRequestHandler
,
181 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
182 this.handleRequestGetDiagnostics
.bind(this) as IncomingRequestHandler
,
185 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
186 this.handleRequestTriggerMessage
.bind(this) as unknown
as IncomingRequestHandler
,
189 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
190 this.handleRequestDataTransfer
.bind(this) as unknown
as IncomingRequestHandler
,
193 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
194 this.handleRequestUpdateFirmware
.bind(this) as unknown
as IncomingRequestHandler
,
197 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
198 this.handleRequestReserveNow
.bind(this) as unknown
as IncomingRequestHandler
,
201 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
202 this.handleRequestCancelReservation
.bind(this) as unknown
as IncomingRequestHandler
,
205 this.jsonSchemas
= new Map
<OCPP16IncomingRequestCommand
, JSONSchemaType
<JsonObject
>>([
207 OCPP16IncomingRequestCommand
.RESET
,
208 OCPP16ServiceUtils
.parseJsonSchemaFile
<ResetRequest
>(
209 'assets/json-schemas/ocpp/1.6/Reset.json',
215 OCPP16IncomingRequestCommand
.CLEAR_CACHE
,
216 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ClearCacheRequest
>(
217 'assets/json-schemas/ocpp/1.6/ClearCache.json',
223 OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
,
224 OCPP16ServiceUtils
.parseJsonSchemaFile
<UnlockConnectorRequest
>(
225 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
231 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
232 OCPP16ServiceUtils
.parseJsonSchemaFile
<GetConfigurationRequest
>(
233 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
239 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
240 OCPP16ServiceUtils
.parseJsonSchemaFile
<ChangeConfigurationRequest
>(
241 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
247 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
248 OCPP16ServiceUtils
.parseJsonSchemaFile
<GetDiagnosticsRequest
>(
249 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
255 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
,
256 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16GetCompositeScheduleRequest
>(
257 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
263 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
264 OCPP16ServiceUtils
.parseJsonSchemaFile
<SetChargingProfileRequest
>(
265 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
271 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
272 OCPP16ServiceUtils
.parseJsonSchemaFile
<ClearChargingProfileRequest
>(
273 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
279 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
280 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ChangeAvailabilityRequest
>(
281 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
287 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
288 OCPP16ServiceUtils
.parseJsonSchemaFile
<RemoteStartTransactionRequest
>(
289 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
295 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
296 OCPP16ServiceUtils
.parseJsonSchemaFile
<RemoteStopTransactionRequest
>(
297 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
303 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
304 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16TriggerMessageRequest
>(
305 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
311 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
312 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16DataTransferRequest
>(
313 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
319 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
320 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16UpdateFirmwareRequest
>(
321 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
327 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
328 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16ReserveNowRequest
>(
329 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
335 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
336 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16CancelReservationRequest
>(
337 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
343 this.validatePayload
= this.validatePayload
.bind(this) as (
344 chargingStation
: ChargingStation
,
345 commandName
: OCPP16IncomingRequestCommand
,
346 commandPayload
: JsonType
,
350 public async incomingRequestHandler
<ReqType
extends JsonType
, ResType
extends JsonType
>(
351 chargingStation
: ChargingStation
,
353 commandName
: OCPP16IncomingRequestCommand
,
354 commandPayload
: ReqType
,
356 let response
: ResType
;
358 chargingStation
.getOcppStrictCompliance() === true &&
359 chargingStation
.inPendingState() === true &&
360 (commandName
=== OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
||
361 commandName
=== OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
)
364 ErrorType
.SECURITY_ERROR
,
365 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
369 )} while the charging station is in pending state on the central server`,
375 chargingStation
.isRegistered() === true ||
376 (chargingStation
.getOcppStrictCompliance() === false &&
377 chargingStation
.inUnknownState() === true)
380 this.incomingRequestHandlers
.has(commandName
) === true &&
381 OCPP16ServiceUtils
.isIncomingRequestCommandSupported(chargingStation
, commandName
) === true
384 this.validatePayload(chargingStation
, commandName
, commandPayload
);
385 // Call the method to build the response
386 response
= (await this.incomingRequestHandlers
.get(commandName
)!(
393 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
394 Handle incoming request error:`,
402 ErrorType
.NOT_IMPLEMENTED
,
403 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
414 ErrorType
.SECURITY_ERROR
,
415 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
419 )} while the charging station is not registered on the central server.`,
424 // Send the built response
425 await chargingStation
.ocppRequestService
.sendResponse(
433 private validatePayload(
434 chargingStation
: ChargingStation
,
435 commandName
: OCPP16IncomingRequestCommand
,
436 commandPayload
: JsonType
,
438 if (this.jsonSchemas
.has(commandName
) === true) {
439 return this.validateIncomingRequestPayload(
442 this.jsonSchemas
.get(commandName
)!,
447 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
448 for command '${commandName}' PDU validation`,
453 // Simulate charging station restart
454 private handleRequestReset(
455 chargingStation
: ChargingStation
,
456 commandPayload
: ResetRequest
,
458 const { type } = commandPayload
;
459 this.runInAsyncScope(
460 chargingStation
.reset
.bind(chargingStation
) as (
461 this: ChargingStation
,
465 `${type}Reset` as OCPP16StopTransactionReason
,
466 ).catch(Constants
.EMPTY_FUNCTION
);
468 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be
469 back online in ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime!)}`,
471 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
474 private async handleRequestUnlockConnector(
475 chargingStation
: ChargingStation
,
476 commandPayload
: UnlockConnectorRequest
,
477 ): Promise
<UnlockConnectorResponse
> {
478 const { connectorId
} = commandPayload
;
479 if (chargingStation
.hasConnector(connectorId
) === false) {
481 `${chargingStation.logPrefix()} Trying to unlock a non existing
482 connector id ${connectorId.toString()}`,
484 return OCPP16Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
;
486 if (connectorId
=== 0) {
488 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`,
490 return OCPP16Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
;
492 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
493 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
495 OCPP16StopTransactionReason
.UNLOCK_COMMAND
,
497 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
498 return OCPP16Constants
.OCPP_RESPONSE_UNLOCKED
;
500 return OCPP16Constants
.OCPP_RESPONSE_UNLOCK_FAILED
;
502 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
505 OCPP16ChargePointStatus
.Available
,
507 return OCPP16Constants
.OCPP_RESPONSE_UNLOCKED
;
510 private handleRequestGetConfiguration(
511 chargingStation
: ChargingStation
,
512 commandPayload
: GetConfigurationRequest
,
513 ): GetConfigurationResponse
{
514 const { key
} = commandPayload
;
515 const configurationKey
: OCPPConfigurationKey
[] = [];
516 const unknownKey
: string[] = [];
517 if (isUndefined(key
) === true) {
518 for (const configuration
of chargingStation
.ocppConfiguration
!.configurationKey
!) {
519 if (isUndefined(configuration
.visible
) === true) {
520 configuration
.visible
= true;
522 if (configuration
.visible
=== false) {
525 configurationKey
.push({
526 key
: configuration
.key
,
527 readonly: configuration
.readonly,
528 value
: configuration
.value
,
531 } else if (isNotEmptyArray(key
) === true) {
532 for (const k
of key
!) {
533 const keyFound
= getConfigurationKey(chargingStation
, k
, true);
535 if (isUndefined(keyFound
.visible
) === true) {
536 keyFound
.visible
= true;
538 if (keyFound
.visible
=== false) {
541 configurationKey
.push({
543 readonly: keyFound
.readonly,
544 value
: keyFound
.value
,
557 private handleRequestChangeConfiguration(
558 chargingStation
: ChargingStation
,
559 commandPayload
: ChangeConfigurationRequest
,
560 ): ChangeConfigurationResponse
{
561 const { key
, value
} = commandPayload
;
562 const keyToChange
= getConfigurationKey(chargingStation
, key
, true);
563 if (keyToChange
?.readonly === true) {
564 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_REJECTED
;
565 } else if (keyToChange
?.readonly === false) {
566 let valueChanged
= false;
567 if (keyToChange
.value
!== value
) {
568 setConfigurationKeyValue(chargingStation
, key
, value
, true);
571 let triggerHeartbeatRestart
= false;
573 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
574 OCPP16StandardParametersKey
.HeartBeatInterval
&&
577 setConfigurationKeyValue(
579 OCPP16StandardParametersKey
.HeartbeatInterval
,
582 triggerHeartbeatRestart
= true;
585 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
586 OCPP16StandardParametersKey
.HeartbeatInterval
&&
589 setConfigurationKeyValue(
591 OCPP16StandardParametersKey
.HeartBeatInterval
,
594 triggerHeartbeatRestart
= true;
596 if (triggerHeartbeatRestart
) {
597 chargingStation
.restartHeartbeat();
600 (keyToChange
.key
as OCPP16StandardParametersKey
) ===
601 OCPP16StandardParametersKey
.WebSocketPingInterval
&&
604 chargingStation
.restartWebSocketPing();
606 if (keyToChange
.reboot
) {
607 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
;
609 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
;
611 return OCPP16Constants
.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
;
614 private handleRequestSetChargingProfile(
615 chargingStation
: ChargingStation
,
616 commandPayload
: SetChargingProfileRequest
,
617 ): SetChargingProfileResponse
{
619 OCPP16ServiceUtils
.checkFeatureProfile(
621 OCPP16SupportedFeatureProfiles
.SmartCharging
,
622 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
625 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
;
627 const { connectorId
, csChargingProfiles
} = commandPayload
;
628 if (chargingStation
.hasConnector(connectorId
) === false) {
630 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
631 non existing connector id ${connectorId}`,
633 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
636 csChargingProfiles
.chargingProfilePurpose
===
637 OCPP16ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
640 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
643 csChargingProfiles
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
&&
644 (connectorId
=== 0 ||
645 chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== false)
648 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
649 on connector ${connectorId} without a started transaction`,
651 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
653 OCPP16ServiceUtils
.setChargingProfile(chargingStation
, connectorId
, csChargingProfiles
);
655 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
658 return OCPP16Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
661 private handleRequestGetCompositeSchedule(
662 chargingStation
: ChargingStation
,
663 commandPayload
: OCPP16GetCompositeScheduleRequest
,
664 ): OCPP16GetCompositeScheduleResponse
{
666 OCPP16ServiceUtils
.checkFeatureProfile(
668 OCPP16SupportedFeatureProfiles
.SmartCharging
,
669 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
,
672 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
674 const { connectorId
, duration
, chargingRateUnit
} = commandPayload
;
675 if (chargingStation
.hasConnector(connectorId
) === false) {
677 `${chargingStation.logPrefix()} Trying to get composite schedule to a
678 non existing connector id ${connectorId}`,
680 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
682 if (connectorId
=== 0) {
684 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`,
686 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
688 if (chargingRateUnit
) {
690 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`,
693 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
696 connectorStatus
?.chargingProfiles
&&
697 isEmptyArray(chargingStation
.getConnectorStatus(0)?.chargingProfiles
),
700 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
702 const currentDate
= new Date();
703 const interval
: Interval
= {
705 end
: addSeconds(currentDate
, duration
),
707 // Get charging profiles sorted by connector id then stack level
708 const storedChargingProfiles
: OCPP16ChargingProfile
[] = getConnectorChargingProfiles(
712 const chargingProfiles
: OCPP16ChargingProfile
[] = [];
713 for (const storedChargingProfile
of storedChargingProfiles
) {
715 isNullOrUndefined(storedChargingProfile
.chargingSchedule
?.startSchedule
) &&
716 connectorStatus
?.transactionStarted
719 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
720 storedChargingProfile.chargingProfileId
721 } has no startSchedule defined. Trying to set it to the connector current transaction start date`,
723 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
724 storedChargingProfile
.chargingSchedule
.startSchedule
= connectorStatus
?.transactionStart
;
727 !isNullOrUndefined(storedChargingProfile
.chargingSchedule
?.startSchedule
) &&
728 isNullOrUndefined(storedChargingProfile
.chargingSchedule
?.duration
)
731 `${chargingStation.logPrefix()} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${
732 storedChargingProfile.chargingProfileId
733 } has no duration defined and will be set to the maximum time allowed`,
735 // OCPP specifies that if duration is not defined, it should be infinite
736 storedChargingProfile
.chargingSchedule
.duration
= differenceInSeconds(
738 storedChargingProfile
.chargingSchedule
.startSchedule
!,
741 if (!isDate(storedChargingProfile
.chargingSchedule
?.startSchedule
)) {
743 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
744 storedChargingProfile.chargingProfileId
745 } startSchedule property is not a Date object. Trying to convert it to a Date object`,
747 storedChargingProfile
.chargingSchedule
.startSchedule
= convertToDate(
748 storedChargingProfile
.chargingSchedule
?.startSchedule
,
752 !prepareChargingProfileKind(
754 storedChargingProfile
,
755 interval
.start
as Date,
756 chargingStation
.logPrefix(),
762 !canProceedChargingProfile(
763 storedChargingProfile
,
764 interval
.start
as Date,
765 chargingStation
.logPrefix(),
770 // Add active charging profiles into chargingProfiles array
772 isValidTime(storedChargingProfile
.chargingSchedule
?.startSchedule
) &&
773 isWithinInterval(storedChargingProfile
.chargingSchedule
.startSchedule
!, interval
)
775 if (isEmptyArray(chargingProfiles
)) {
779 storedChargingProfile
.chargingSchedule
.startSchedule
!,
780 storedChargingProfile
.chargingSchedule
.duration
!,
785 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
=
786 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.filter(
790 storedChargingProfile
.chargingSchedule
.startSchedule
!,
791 schedulePeriod
.startPeriod
,
796 storedChargingProfile
.chargingSchedule
.duration
= differenceInSeconds(
798 storedChargingProfile
.chargingSchedule
.startSchedule
!,
801 chargingProfiles
.push(storedChargingProfile
);
802 } else if (isNotEmptyArray(chargingProfiles
)) {
803 const chargingProfilesInterval
: Interval
= {
805 chargingProfiles
.map(
806 (chargingProfile
) => chargingProfile
.chargingSchedule
.startSchedule
?? maxTime
,
810 chargingProfiles
.map(
813 chargingProfile
.chargingSchedule
.startSchedule
!,
814 chargingProfile
.chargingSchedule
.duration
!,
819 let addChargingProfile
= false;
821 isBefore(interval
.start
, chargingProfilesInterval
.start
) &&
823 storedChargingProfile
.chargingSchedule
.startSchedule
!,
824 chargingProfilesInterval
.start
,
827 // Remove charging schedule periods that are after the start of the active profiles interval
828 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
=
829 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.filter(
833 storedChargingProfile
.chargingSchedule
.startSchedule
!,
834 schedulePeriod
.startPeriod
,
837 start
: interval
.start
,
838 end
: chargingProfilesInterval
.start
,
842 addChargingProfile
= true;
845 isBefore(chargingProfilesInterval
.end
, interval
.end
) &&
848 storedChargingProfile
.chargingSchedule
.startSchedule
!,
849 storedChargingProfile
.chargingSchedule
.duration
!,
851 chargingProfilesInterval
.end
,
854 // Remove charging schedule periods that are before the end of the active profiles interval
855 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
=
856 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.filter(
857 (schedulePeriod
, index
) => {
861 storedChargingProfile
.chargingSchedule
.startSchedule
!,
862 schedulePeriod
.startPeriod
,
865 start
: chargingProfilesInterval
.end
,
875 storedChargingProfile
.chargingSchedule
.startSchedule
!,
876 schedulePeriod
.startPeriod
,
879 start
: chargingProfilesInterval
.end
,
884 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
887 storedChargingProfile
.chargingSchedule
.startSchedule
!,
888 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
[index
+ 1]
892 start
: chargingProfilesInterval
.end
,
902 addChargingProfile
= true;
904 addChargingProfile
&& chargingProfiles
.push(storedChargingProfile
);
908 const compositeScheduleStart
: Date = min(
909 chargingProfiles
.map(
910 (chargingProfile
) => chargingProfile
.chargingSchedule
.startSchedule
?? maxTime
,
913 const compositeScheduleDuration
: number = Math.max(
914 ...chargingProfiles
.map((chargingProfile
) =>
915 isNaN(chargingProfile
.chargingSchedule
.duration
!)
917 : chargingProfile
.chargingSchedule
.duration
!,
920 const compositeSchedulePeriods
: OCPP16ChargingSchedulePeriod
[] = chargingProfiles
921 .map((chargingProfile
) => chargingProfile
.chargingSchedule
.chargingSchedulePeriod
)
923 (accumulator
, value
) =>
924 accumulator
.concat(value
).sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
927 const compositeSchedule
: OCPP16ChargingSchedule
= {
928 startSchedule
: compositeScheduleStart
,
929 duration
: compositeScheduleDuration
,
930 chargingRateUnit
: chargingProfiles
.every(
932 chargingProfile
.chargingSchedule
.chargingRateUnit
=== OCPP16ChargingRateUnitType
.AMPERE
,
934 ? OCPP16ChargingRateUnitType
.AMPERE
935 : chargingProfiles
.every(
937 chargingProfile
.chargingSchedule
.chargingRateUnit
=== OCPP16ChargingRateUnitType
.WATT
,
939 ? OCPP16ChargingRateUnitType
.WATT
940 : OCPP16ChargingRateUnitType
.AMPERE
,
941 chargingSchedulePeriod
: compositeSchedulePeriods
,
942 minChargeRate
: Math.min(
943 ...chargingProfiles
.map((chargingProfile
) =>
944 isNaN(chargingProfile
.chargingSchedule
.minChargeRate
!)
946 : chargingProfile
.chargingSchedule
.minChargeRate
!,
951 status: GenericStatus
.Accepted
,
952 scheduleStart
: compositeSchedule
.startSchedule
!,
954 chargingSchedule
: compositeSchedule
,
958 private handleRequestClearChargingProfile(
959 chargingStation
: ChargingStation
,
960 commandPayload
: ClearChargingProfileRequest
,
961 ): ClearChargingProfileResponse
{
963 OCPP16ServiceUtils
.checkFeatureProfile(
965 OCPP16SupportedFeatureProfiles
.SmartCharging
,
966 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
969 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
971 const { connectorId
} = commandPayload
;
972 if (chargingStation
.hasConnector(connectorId
!) === false) {
974 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
975 a non existing connector id ${connectorId}`,
977 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
980 !isNullOrUndefined(connectorId
) &&
981 isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
!)?.chargingProfiles
)
983 chargingStation
.getConnectorStatus(connectorId
!)!.chargingProfiles
= [];
985 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`,
987 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
989 if (isNullOrUndefined(connectorId
)) {
990 let clearedCP
= false;
991 if (chargingStation
.hasEvses
) {
992 for (const evseStatus
of chargingStation
.evses
.values()) {
993 for (const connectorStatus
of evseStatus
.connectors
.values()) {
994 clearedCP
= OCPP16ServiceUtils
.clearChargingProfiles(
997 connectorStatus
.chargingProfiles
,
1002 for (const id
of chargingStation
.connectors
.keys()) {
1003 clearedCP
= OCPP16ServiceUtils
.clearChargingProfiles(
1006 chargingStation
.getConnectorStatus(id
)?.chargingProfiles
,
1011 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
1014 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
1017 private async handleRequestChangeAvailability(
1018 chargingStation
: ChargingStation
,
1019 commandPayload
: OCPP16ChangeAvailabilityRequest
,
1020 ): Promise
<OCPP16ChangeAvailabilityResponse
> {
1021 const { connectorId
, type } = commandPayload
;
1022 if (chargingStation
.hasConnector(connectorId
) === false) {
1024 `${chargingStation.logPrefix()} Trying to change the availability of a
1025 non existing connector id ${connectorId.toString()}`,
1027 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
1029 const chargePointStatus
: OCPP16ChargePointStatus
=
1030 type === OCPP16AvailabilityType
.Operative
1031 ? OCPP16ChargePointStatus
.Available
1032 : OCPP16ChargePointStatus
.Unavailable
;
1033 if (connectorId
=== 0) {
1034 let response
: OCPP16ChangeAvailabilityResponse
;
1035 if (chargingStation
.hasEvses
) {
1036 for (const evseStatus
of chargingStation
.evses
.values()) {
1037 response
= await OCPP16ServiceUtils
.changeAvailability(
1039 [...evseStatus
.connectors
.keys()],
1045 response
= await OCPP16ServiceUtils
.changeAvailability(
1047 [...chargingStation
.connectors
.keys()],
1055 (chargingStation
.isChargingStationAvailable() === true ||
1056 (chargingStation
.isChargingStationAvailable() === false &&
1057 type === OCPP16AvailabilityType
.Inoperative
))
1059 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
1060 chargingStation
.getConnectorStatus(connectorId
)!.availability
= type;
1061 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
1063 chargingStation
.getConnectorStatus(connectorId
)!.availability
= type;
1064 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1069 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
1071 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
1074 private async handleRequestRemoteStartTransaction(
1075 chargingStation
: ChargingStation
,
1076 commandPayload
: RemoteStartTransactionRequest
,
1077 ): Promise
<GenericResponse
> {
1078 const { connectorId
: transactionConnectorId
, idTag
, chargingProfile
} = commandPayload
;
1079 if (chargingStation
.hasConnector(transactionConnectorId
) === false) {
1080 return this.notifyRemoteStartTransactionRejected(
1082 transactionConnectorId
,
1087 !chargingStation
.isChargingStationAvailable() ||
1088 !chargingStation
.isConnectorAvailable(transactionConnectorId
)
1090 return this.notifyRemoteStartTransactionRejected(
1092 transactionConnectorId
,
1096 const remoteStartTransactionLogMsg
= `
1097 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
1098 chargingStation.stationInfo.chargingStationId
1099 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
1100 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1102 transactionConnectorId
,
1103 OCPP16ChargePointStatus
.Preparing
,
1105 const connectorStatus
= chargingStation
.getConnectorStatus(transactionConnectorId
)!;
1106 // Authorization check required
1108 chargingStation
.getAuthorizeRemoteTxRequests() === true &&
1109 (await OCPP16ServiceUtils
.isIdTagAuthorized(chargingStation
, transactionConnectorId
, idTag
))
1111 // Authorization successful, start transaction
1113 this.setRemoteStartTransactionChargingProfile(
1115 transactionConnectorId
,
1119 connectorStatus
.transactionRemoteStarted
= true;
1122 await chargingStation
.ocppRequestService
.requestHandler
<
1123 OCPP16StartTransactionRequest
,
1124 OCPP16StartTransactionResponse
1125 >(chargingStation
, OCPP16RequestCommand
.START_TRANSACTION
, {
1126 connectorId
: transactionConnectorId
,
1129 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
1131 logger
.debug(remoteStartTransactionLogMsg
);
1132 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
1134 return this.notifyRemoteStartTransactionRejected(
1136 transactionConnectorId
,
1140 return this.notifyRemoteStartTransactionRejected(
1142 transactionConnectorId
,
1146 // No authorization check required, start transaction
1148 this.setRemoteStartTransactionChargingProfile(
1150 transactionConnectorId
,
1154 connectorStatus
.transactionRemoteStarted
= true;
1157 await chargingStation
.ocppRequestService
.requestHandler
<
1158 OCPP16StartTransactionRequest
,
1159 OCPP16StartTransactionResponse
1160 >(chargingStation
, OCPP16RequestCommand
.START_TRANSACTION
, {
1161 connectorId
: transactionConnectorId
,
1164 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
1166 logger
.debug(remoteStartTransactionLogMsg
);
1167 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
1169 return this.notifyRemoteStartTransactionRejected(
1171 transactionConnectorId
,
1175 return this.notifyRemoteStartTransactionRejected(
1177 transactionConnectorId
,
1182 private async notifyRemoteStartTransactionRejected(
1183 chargingStation
: ChargingStation
,
1184 connectorId
: number,
1186 ): Promise
<GenericResponse
> {
1188 chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.Available
1190 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1193 OCPP16ChargePointStatus
.Available
,
1197 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
1198 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
1200 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
1202 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
1205 private setRemoteStartTransactionChargingProfile(
1206 chargingStation
: ChargingStation
,
1207 connectorId
: number,
1208 chargingProfile
: OCPP16ChargingProfile
,
1212 chargingProfile
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
1214 OCPP16ServiceUtils
.setChargingProfile(chargingStation
, connectorId
, chargingProfile
);
1216 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1217 on connector id ${connectorId}: %j`,
1223 chargingProfile
.chargingProfilePurpose
!== OCPP16ChargingProfilePurposeType
.TX_PROFILE
1226 `${chargingStation.logPrefix()} Not allowed to set ${
1227 chargingProfile.chargingProfilePurpose
1228 } charging profile(s) at remote start transaction`,
1235 private async handleRequestRemoteStopTransaction(
1236 chargingStation
: ChargingStation
,
1237 commandPayload
: RemoteStopTransactionRequest
,
1238 ): Promise
<GenericResponse
> {
1239 const { transactionId
} = commandPayload
;
1240 if (chargingStation
.hasEvses
) {
1241 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1243 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1244 if (connectorStatus
.transactionId
=== transactionId
) {
1245 return OCPP16ServiceUtils
.remoteStopTransaction(chargingStation
, connectorId
);
1251 for (const connectorId
of chargingStation
.connectors
.keys()) {
1254 chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
1256 return OCPP16ServiceUtils
.remoteStopTransaction(chargingStation
, connectorId
);
1261 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
1262 ${transactionId.toString()}`,
1264 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
1267 private handleRequestUpdateFirmware(
1268 chargingStation
: ChargingStation
,
1269 commandPayload
: OCPP16UpdateFirmwareRequest
,
1270 ): OCPP16UpdateFirmwareResponse
{
1272 OCPP16ServiceUtils
.checkFeatureProfile(
1274 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
1275 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
1279 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1280 Cannot simulate firmware update: feature profile not supported`,
1282 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1284 let { retrieveDate
} = commandPayload
;
1286 !isNullOrUndefined(chargingStation
.stationInfo
.firmwareStatus
) &&
1287 chargingStation
.stationInfo
.firmwareStatus
!== OCPP16FirmwareStatus
.Installed
1290 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1291 Cannot simulate firmware update: firmware update is already in progress`,
1293 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1295 retrieveDate
= convertToDate(retrieveDate
)!;
1296 const now
= Date.now();
1297 if (retrieveDate
?.getTime() <= now
) {
1298 this.runInAsyncScope(
1299 this.updateFirmwareSimulation
.bind(this) as (
1300 this: OCPP16IncomingRequestService
,
1305 ).catch(Constants
.EMPTY_FUNCTION
);
1309 this.runInAsyncScope(
1310 this.updateFirmwareSimulation
.bind(this) as (
1311 this: OCPP16IncomingRequestService
,
1316 ).catch(Constants
.EMPTY_FUNCTION
);
1318 retrieveDate
?.getTime() - now
,
1321 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1324 private async updateFirmwareSimulation(
1325 chargingStation
: ChargingStation
,
1329 if (checkChargingStation(chargingStation
, chargingStation
.logPrefix()) === false) {
1332 if (chargingStation
.hasEvses
) {
1333 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1335 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1336 if (connectorStatus
?.transactionStarted
=== false) {
1337 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1340 OCPP16ChargePointStatus
.Unavailable
,
1347 for (const connectorId
of chargingStation
.connectors
.keys()) {
1350 chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== false
1352 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1355 OCPP16ChargePointStatus
.Unavailable
,
1360 await chargingStation
.ocppRequestService
.requestHandler
<
1361 OCPP16FirmwareStatusNotificationRequest
,
1362 OCPP16FirmwareStatusNotificationResponse
1363 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1364 status: OCPP16FirmwareStatus
.Downloading
,
1366 chargingStation
.stationInfo
.firmwareStatus
= OCPP16FirmwareStatus
.Downloading
;
1368 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
===
1369 OCPP16FirmwareStatus
.DownloadFailed
1371 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1372 await chargingStation
.ocppRequestService
.requestHandler
<
1373 OCPP16FirmwareStatusNotificationRequest
,
1374 OCPP16FirmwareStatusNotificationResponse
1375 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1376 status: chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
,
1378 chargingStation
.stationInfo
.firmwareStatus
=
1379 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
;
1382 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1383 await chargingStation
.ocppRequestService
.requestHandler
<
1384 OCPP16FirmwareStatusNotificationRequest
,
1385 OCPP16FirmwareStatusNotificationResponse
1386 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1387 status: OCPP16FirmwareStatus
.Downloaded
,
1389 chargingStation
.stationInfo
.firmwareStatus
= OCPP16FirmwareStatus
.Downloaded
;
1390 let wasTransactionsStarted
= false;
1391 let transactionsStarted
: boolean;
1393 const runningTransactions
= chargingStation
.getNumberOfRunningTransactions();
1394 if (runningTransactions
> 0) {
1395 const waitTime
= secondsToMilliseconds(15);
1397 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1398 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1400 )} before continuing firmware update simulation`,
1402 await sleep(waitTime
);
1403 transactionsStarted
= true;
1404 wasTransactionsStarted
= true;
1406 if (chargingStation
.hasEvses
) {
1407 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1409 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1410 if (connectorStatus
?.status !== OCPP16ChargePointStatus
.Unavailable
) {
1411 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1414 OCPP16ChargePointStatus
.Unavailable
,
1421 for (const connectorId
of chargingStation
.connectors
.keys()) {
1424 chargingStation
.getConnectorStatus(connectorId
)?.status !==
1425 OCPP16ChargePointStatus
.Unavailable
1427 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1430 OCPP16ChargePointStatus
.Unavailable
,
1435 transactionsStarted
= false;
1437 } while (transactionsStarted
);
1438 !wasTransactionsStarted
&&
1439 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
))));
1440 if (checkChargingStation(chargingStation
, chargingStation
.logPrefix()) === false) {
1443 await chargingStation
.ocppRequestService
.requestHandler
<
1444 OCPP16FirmwareStatusNotificationRequest
,
1445 OCPP16FirmwareStatusNotificationResponse
1446 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1447 status: OCPP16FirmwareStatus
.Installing
,
1449 chargingStation
.stationInfo
.firmwareStatus
= OCPP16FirmwareStatus
.Installing
;
1451 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
===
1452 OCPP16FirmwareStatus
.InstallationFailed
1454 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1455 await chargingStation
.ocppRequestService
.requestHandler
<
1456 OCPP16FirmwareStatusNotificationRequest
,
1457 OCPP16FirmwareStatusNotificationResponse
1458 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1459 status: chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
,
1461 chargingStation
.stationInfo
.firmwareStatus
=
1462 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
;
1465 if (chargingStation
.stationInfo
?.firmwareUpgrade
?.reset
=== true) {
1466 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1467 await chargingStation
.reset(OCPP16StopTransactionReason
.REBOOT
);
1471 private async handleRequestGetDiagnostics(
1472 chargingStation
: ChargingStation
,
1473 commandPayload
: GetDiagnosticsRequest
,
1474 ): Promise
<GetDiagnosticsResponse
> {
1476 OCPP16ServiceUtils
.checkFeatureProfile(
1478 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
1479 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1483 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1484 Cannot get diagnostics: feature profile not supported`,
1486 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1488 const { location
} = commandPayload
;
1489 const uri
= new URL(location
);
1490 if (uri
.protocol
.startsWith('ftp:')) {
1491 let ftpClient
: Client
| undefined;
1493 const logFiles
= readdirSync(resolve(dirname(fileURLToPath(import.meta
.url
)), '../'))
1494 .filter((file
) => file
.endsWith('.log'))
1495 .map((file
) => join('./', file
));
1496 const diagnosticsArchive
= `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1497 create({ gzip
: true }, logFiles
).pipe(createWriteStream(diagnosticsArchive
));
1498 ftpClient
= new Client();
1499 const accessResponse
= await ftpClient
.access({
1501 ...(isNotEmptyString(uri
.port
) && { port
: convertToInt(uri
.port
) }),
1502 ...(isNotEmptyString(uri
.username
) && { user
: uri
.username
}),
1503 ...(isNotEmptyString(uri
.password
) && { password
: uri
.password
}),
1505 let uploadResponse
: FTPResponse
| undefined;
1506 if (accessResponse
.code
=== 220) {
1507 ftpClient
.trackProgress((info
) => {
1509 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1511 } bytes transferred from diagnostics archive ${info.name}`,
1513 chargingStation
.ocppRequestService
1515 OCPP16DiagnosticsStatusNotificationRequest
,
1516 OCPP16DiagnosticsStatusNotificationResponse
1517 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1518 status: OCPP16DiagnosticsStatus
.Uploading
,
1522 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1523 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1528 uploadResponse
= await ftpClient
.uploadFrom(
1529 join(resolve(dirname(fileURLToPath(import.meta
.url
)), '../'), diagnosticsArchive
),
1530 `${uri.pathname}${diagnosticsArchive}`,
1532 if (uploadResponse
.code
=== 226) {
1533 await chargingStation
.ocppRequestService
.requestHandler
<
1534 OCPP16DiagnosticsStatusNotificationRequest
,
1535 OCPP16DiagnosticsStatusNotificationResponse
1536 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1537 status: OCPP16DiagnosticsStatus
.Uploaded
,
1542 return { fileName
: diagnosticsArchive
};
1544 throw new OCPPError(
1545 ErrorType
.GENERIC_ERROR
,
1546 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1547 uploadResponse?.code && `|${uploadResponse?.code.toString()}
`
1549 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1552 throw new OCPPError(
1553 ErrorType
.GENERIC_ERROR
,
1554 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1555 uploadResponse?.code && `|${uploadResponse?.code.toString()}
`
1557 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1560 await chargingStation
.ocppRequestService
.requestHandler
<
1561 OCPP16DiagnosticsStatusNotificationRequest
,
1562 OCPP16DiagnosticsStatusNotificationResponse
1563 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1564 status: OCPP16DiagnosticsStatus
.UploadFailed
,
1569 return this.handleIncomingRequestError
<GetDiagnosticsResponse
>(
1571 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1573 { errorResponse
: OCPP16Constants
.OCPP_RESPONSE_EMPTY
},
1578 `${chargingStation.logPrefix()} Unsupported protocol ${
1580 } to transfer the diagnostic logs archive`,
1582 await chargingStation
.ocppRequestService
.requestHandler
<
1583 OCPP16DiagnosticsStatusNotificationRequest
,
1584 OCPP16DiagnosticsStatusNotificationResponse
1585 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1586 status: OCPP16DiagnosticsStatus
.UploadFailed
,
1588 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1592 private handleRequestTriggerMessage(
1593 chargingStation
: ChargingStation
,
1594 commandPayload
: OCPP16TriggerMessageRequest
,
1595 ): OCPP16TriggerMessageResponse
{
1596 const { requestedMessage
, connectorId
} = commandPayload
;
1598 !OCPP16ServiceUtils
.checkFeatureProfile(
1600 OCPP16SupportedFeatureProfiles
.RemoteTrigger
,
1601 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1603 !OCPP16ServiceUtils
.isMessageTriggerSupported(chargingStation
, requestedMessage
)
1605 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1608 !OCPP16ServiceUtils
.isConnectorIdValid(
1610 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1614 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
;
1617 switch (requestedMessage
) {
1618 case OCPP16MessageTrigger
.BootNotification
:
1620 chargingStation
.ocppRequestService
1621 .requestHandler
<OCPP16BootNotificationRequest
, OCPP16BootNotificationResponse
>(
1623 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
1624 chargingStation
.bootNotificationRequest
,
1625 { skipBufferingOnError
: true, triggerMessage
: true },
1627 .then((response
) => {
1628 chargingStation
.bootNotificationResponse
= response
;
1630 .catch(Constants
.EMPTY_FUNCTION
);
1631 }, OCPP16Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1632 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1633 case OCPP16MessageTrigger
.Heartbeat
:
1635 chargingStation
.ocppRequestService
1636 .requestHandler
<OCPP16HeartbeatRequest
, OCPP16HeartbeatResponse
>(
1638 OCPP16RequestCommand
.HEARTBEAT
,
1641 triggerMessage
: true,
1644 .catch(Constants
.EMPTY_FUNCTION
);
1645 }, OCPP16Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1646 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1647 case OCPP16MessageTrigger
.StatusNotification
:
1649 if (!isNullOrUndefined(connectorId
)) {
1650 chargingStation
.ocppRequestService
1651 .requestHandler
<OCPP16StatusNotificationRequest
, OCPP16StatusNotificationResponse
>(
1653 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1656 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1657 status: chargingStation
.getConnectorStatus(connectorId
!)?.status,
1660 triggerMessage
: true,
1663 .catch(Constants
.EMPTY_FUNCTION
);
1665 // eslint-disable-next-line no-lonely-if
1666 if (chargingStation
.hasEvses
) {
1667 for (const evseStatus
of chargingStation
.evses
.values()) {
1668 for (const [id
, connectorStatus
] of evseStatus
.connectors
) {
1669 chargingStation
.ocppRequestService
1671 OCPP16StatusNotificationRequest
,
1672 OCPP16StatusNotificationResponse
1675 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1678 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1679 status: connectorStatus
.status,
1682 triggerMessage
: true,
1685 .catch(Constants
.EMPTY_FUNCTION
);
1689 for (const id
of chargingStation
.connectors
.keys()) {
1690 chargingStation
.ocppRequestService
1692 OCPP16StatusNotificationRequest
,
1693 OCPP16StatusNotificationResponse
1696 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1699 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1700 status: chargingStation
.getConnectorStatus(id
)?.status,
1703 triggerMessage
: true,
1706 .catch(Constants
.EMPTY_FUNCTION
);
1710 }, OCPP16Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1711 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1713 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1716 return this.handleIncomingRequestError
<OCPP16TriggerMessageResponse
>(
1718 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1720 { errorResponse
: OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
},
1725 private handleRequestDataTransfer(
1726 chargingStation
: ChargingStation
,
1727 commandPayload
: OCPP16DataTransferRequest
,
1728 ): OCPP16DataTransferResponse
{
1729 const { vendorId
} = commandPayload
;
1731 if (Object.values(OCPP16DataTransferVendorId
).includes(vendorId
)) {
1732 return OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
;
1734 return OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
;
1736 return this.handleIncomingRequestError
<OCPP16DataTransferResponse
>(
1738 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
1740 { errorResponse
: OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_REJECTED
},
1745 private async handleRequestReserveNow(
1746 chargingStation
: ChargingStation
,
1747 commandPayload
: OCPP16ReserveNowRequest
,
1748 ): Promise
<OCPP16ReserveNowResponse
> {
1750 !OCPP16ServiceUtils
.checkFeatureProfile(
1752 OCPP16SupportedFeatureProfiles
.Reservation
,
1753 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
1756 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1758 const { reservationId
, idTag
, connectorId
} = commandPayload
;
1759 let response
: OCPP16ReserveNowResponse
;
1761 if (connectorId
> 0 && !chargingStation
.isConnectorAvailable(connectorId
)) {
1762 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1764 if (connectorId
=== 0 && !chargingStation
.getReserveConnectorZeroSupported()) {
1765 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1767 if (!(await OCPP16ServiceUtils
.isIdTagAuthorized(chargingStation
, connectorId
, idTag
))) {
1768 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1770 await removeExpiredReservations(chargingStation
);
1771 switch (chargingStation
.getConnectorStatus(connectorId
)!.status) {
1772 case OCPP16ChargePointStatus
.Faulted
:
1773 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_FAULTED
;
1775 case OCPP16ChargePointStatus
.Preparing
:
1776 case OCPP16ChargePointStatus
.Charging
:
1777 case OCPP16ChargePointStatus
.SuspendedEV
:
1778 case OCPP16ChargePointStatus
.SuspendedEVSE
:
1779 case OCPP16ChargePointStatus
.Finishing
:
1780 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
;
1782 case OCPP16ChargePointStatus
.Unavailable
:
1783 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
;
1785 case OCPP16ChargePointStatus
.Reserved
:
1786 if (!chargingStation
.isConnectorReservable(reservationId
, idTag
, connectorId
)) {
1787 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
;
1790 // eslint-disable-next-line no-fallthrough
1792 if (!chargingStation
.isConnectorReservable(reservationId
, idTag
)) {
1793 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
;
1796 await chargingStation
.addReservation({
1797 id
: commandPayload
.reservationId
,
1800 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_ACCEPTED
;
1805 chargingStation
.getConnectorStatus(connectorId
)!.status = OCPP16ChargePointStatus
.Available
;
1806 return this.handleIncomingRequestError
<OCPP16ReserveNowResponse
>(
1808 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
1810 { errorResponse
: OCPP16Constants
.OCPP_RESERVATION_RESPONSE_FAULTED
},
1815 private async handleRequestCancelReservation(
1816 chargingStation
: ChargingStation
,
1817 commandPayload
: OCPP16CancelReservationRequest
,
1818 ): Promise
<GenericResponse
> {
1820 !OCPP16ServiceUtils
.checkFeatureProfile(
1822 OCPP16SupportedFeatureProfiles
.Reservation
,
1823 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
1826 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
;
1829 const { reservationId
} = commandPayload
;
1830 const reservation
= chargingStation
.getReservationBy('reservationId', reservationId
);
1831 if (isUndefined(reservation
)) {
1833 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1834 does not exist on charging station`,
1836 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
;
1838 await chargingStation
.removeReservation(
1840 ReservationTerminationReason
.RESERVATION_CANCELED
,
1842 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
;
1844 return this.handleIncomingRequestError
<GenericResponse
>(
1846 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
1848 { errorResponse
: OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
},