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 prepareChargingProfileKind
,
32 removeExpiredReservations
,
33 setConfigurationKeyValue
,
34 } from
'../../../charging-station';
35 import { OCPPError
} from
'../../../exception';
37 type ChangeConfigurationRequest
,
38 type ChangeConfigurationResponse
,
39 type ClearChargingProfileRequest
,
40 type ClearChargingProfileResponse
,
44 type GetConfigurationRequest
,
45 type GetConfigurationResponse
,
46 type GetDiagnosticsRequest
,
47 type GetDiagnosticsResponse
,
48 type IncomingRequestHandler
,
51 OCPP16AuthorizationStatus
,
52 OCPP16AvailabilityType
,
53 type OCPP16BootNotificationRequest
,
54 type OCPP16BootNotificationResponse
,
55 type OCPP16CancelReservationRequest
,
56 type OCPP16ChangeAvailabilityRequest
,
57 type OCPP16ChangeAvailabilityResponse
,
58 OCPP16ChargePointErrorCode
,
59 OCPP16ChargePointStatus
,
60 type OCPP16ChargingProfile
,
61 OCPP16ChargingProfilePurposeType
,
62 OCPP16ChargingRateUnitType
,
63 type OCPP16ChargingSchedule
,
64 type OCPP16ChargingSchedulePeriod
,
65 type OCPP16ClearCacheRequest
,
66 type OCPP16DataTransferRequest
,
67 type OCPP16DataTransferResponse
,
68 OCPP16DataTransferVendorId
,
69 OCPP16DiagnosticsStatus
,
70 type OCPP16DiagnosticsStatusNotificationRequest
,
71 type OCPP16DiagnosticsStatusNotificationResponse
,
73 type OCPP16FirmwareStatusNotificationRequest
,
74 type OCPP16FirmwareStatusNotificationResponse
,
75 type OCPP16GetCompositeScheduleRequest
,
76 type OCPP16GetCompositeScheduleResponse
,
77 type OCPP16HeartbeatRequest
,
78 type OCPP16HeartbeatResponse
,
79 OCPP16IncomingRequestCommand
,
82 type OCPP16ReserveNowRequest
,
83 type OCPP16ReserveNowResponse
,
84 OCPP16StandardParametersKey
,
85 type OCPP16StartTransactionRequest
,
86 type OCPP16StartTransactionResponse
,
87 type OCPP16StatusNotificationRequest
,
88 type OCPP16StatusNotificationResponse
,
89 OCPP16StopTransactionReason
,
90 OCPP16SupportedFeatureProfiles
,
91 type OCPP16TriggerMessageRequest
,
92 type OCPP16TriggerMessageResponse
,
93 type OCPP16UpdateFirmwareRequest
,
94 type OCPP16UpdateFirmwareResponse
,
95 type OCPPConfigurationKey
,
97 type RemoteStartTransactionRequest
,
98 type RemoteStopTransactionRequest
,
99 ReservationTerminationReason
,
101 type SetChargingProfileRequest
,
102 type SetChargingProfileResponse
,
103 type UnlockConnectorRequest
,
104 type UnlockConnectorResponse
,
105 } 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 const storedChargingProfiles
: OCPP16ChargingProfile
[] = cloneObject
<OCPP16ChargingProfile
[]>(
708 (connectorStatus
?.chargingProfiles
?? []).concat(
709 chargingStation
.getConnectorStatus(0)?.chargingProfiles
?? [],
711 ).sort((a
, b
) => b
.stackLevel
- a
.stackLevel
);
712 const chargingProfiles
: OCPP16ChargingProfile
[] = [];
713 for (const storedChargingProfile
of storedChargingProfiles
) {
715 connectorStatus
?.transactionStarted
&&
716 isNullOrUndefined(storedChargingProfile
.chargingSchedule
?.startSchedule
)
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
;
726 if (!isDate(storedChargingProfile
.chargingSchedule
?.startSchedule
)) {
728 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
729 storedChargingProfile.chargingProfileId
730 } startSchedule property is not a Date object. Trying to convert it to a Date object`,
732 storedChargingProfile
.chargingSchedule
.startSchedule
= convertToDate(
733 storedChargingProfile
.chargingSchedule
?.startSchedule
,
737 !prepareChargingProfileKind(
739 storedChargingProfile
,
740 interval
.start
as Date,
741 chargingStation
.logPrefix(),
747 !canProceedChargingProfile(
748 storedChargingProfile
,
749 interval
.start
as Date,
750 chargingStation
.logPrefix(),
755 // Add active charging profiles into chargingProfiles array
757 isValidTime(storedChargingProfile
.chargingSchedule
?.startSchedule
) &&
758 isWithinInterval(storedChargingProfile
.chargingSchedule
.startSchedule
!, interval
)
760 if (isEmptyArray(chargingProfiles
)) {
764 storedChargingProfile
.chargingSchedule
.startSchedule
!,
765 storedChargingProfile
.chargingSchedule
.duration
!,
770 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
=
771 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.filter(
775 storedChargingProfile
.chargingSchedule
.startSchedule
!,
776 schedulePeriod
.startPeriod
,
781 storedChargingProfile
.chargingSchedule
.duration
= differenceInSeconds(
783 storedChargingProfile
.chargingSchedule
.startSchedule
!,
786 chargingProfiles
.push(storedChargingProfile
);
787 } else if (isNotEmptyArray(chargingProfiles
)) {
788 const chargingProfilesInterval
: Interval
= {
790 chargingProfiles
.map(
791 (chargingProfile
) => chargingProfile
.chargingSchedule
.startSchedule
?? maxTime
,
795 chargingProfiles
.map(
798 chargingProfile
.chargingSchedule
.startSchedule
!,
799 chargingProfile
.chargingSchedule
.duration
!,
804 let addChargingProfile
= false;
806 isBefore(interval
.start
, chargingProfilesInterval
.start
) &&
808 storedChargingProfile
.chargingSchedule
.startSchedule
!,
809 chargingProfilesInterval
.start
,
812 // Remove charging schedule periods that are after the start of the active profiles interval
813 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
=
814 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.filter(
818 storedChargingProfile
.chargingSchedule
.startSchedule
!,
819 schedulePeriod
.startPeriod
,
822 start
: interval
.start
,
823 end
: chargingProfilesInterval
.start
,
827 addChargingProfile
= true;
830 isBefore(chargingProfilesInterval
.end
, interval
.end
) &&
833 storedChargingProfile
.chargingSchedule
.startSchedule
!,
834 storedChargingProfile
.chargingSchedule
.duration
!,
836 chargingProfilesInterval
.end
,
839 // Remove charging schedule periods that are before the end of the active profiles interval
840 // FIXME: can lead to a gap in the charging schedule: chargingProfilesInterval.end -> first matching schedulePeriod.startPeriod
841 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
=
842 storedChargingProfile
.chargingSchedule
.chargingSchedulePeriod
.filter(
846 storedChargingProfile
.chargingSchedule
.startSchedule
!,
847 schedulePeriod
.startPeriod
,
850 start
: chargingProfilesInterval
.end
,
855 addChargingProfile
= true;
857 addChargingProfile
&& chargingProfiles
.push(storedChargingProfile
);
861 const compositeScheduleStart
: Date = min(
862 chargingProfiles
.map(
863 (chargingProfile
) => chargingProfile
.chargingSchedule
.startSchedule
?? maxTime
,
866 const compositeScheduleDuration
: number = Math.max(
867 ...chargingProfiles
.map((chargingProfile
) =>
868 isNaN(chargingProfile
.chargingSchedule
.duration
!)
870 : chargingProfile
.chargingSchedule
.duration
!,
873 const compositeSchedulePeriods
: OCPP16ChargingSchedulePeriod
[] = chargingProfiles
874 .map((chargingProfile
) => chargingProfile
.chargingSchedule
.chargingSchedulePeriod
)
876 (accumulator
, value
) =>
877 accumulator
.concat(value
).sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
880 const compositeSchedule
: OCPP16ChargingSchedule
= {
881 startSchedule
: compositeScheduleStart
,
882 duration
: compositeScheduleDuration
,
883 chargingRateUnit
: chargingProfiles
.every(
885 chargingProfile
.chargingSchedule
.chargingRateUnit
=== OCPP16ChargingRateUnitType
.AMPERE
,
887 ? OCPP16ChargingRateUnitType
.AMPERE
888 : chargingProfiles
.every(
890 chargingProfile
.chargingSchedule
.chargingRateUnit
=== OCPP16ChargingRateUnitType
.WATT
,
892 ? OCPP16ChargingRateUnitType
.WATT
893 : OCPP16ChargingRateUnitType
.AMPERE
,
894 chargingSchedulePeriod
: compositeSchedulePeriods
,
895 minChargeRate
: Math.min(
896 ...chargingProfiles
.map((chargingProfile
) =>
897 isNaN(chargingProfile
.chargingSchedule
.minChargeRate
!)
899 : chargingProfile
.chargingSchedule
.minChargeRate
!,
904 status: GenericStatus
.Accepted
,
905 scheduleStart
: compositeSchedule
.startSchedule
!,
907 chargingSchedule
: compositeSchedule
,
911 private handleRequestClearChargingProfile(
912 chargingStation
: ChargingStation
,
913 commandPayload
: ClearChargingProfileRequest
,
914 ): ClearChargingProfileResponse
{
916 OCPP16ServiceUtils
.checkFeatureProfile(
918 OCPP16SupportedFeatureProfiles
.SmartCharging
,
919 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
922 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
924 const { connectorId
} = commandPayload
;
925 if (chargingStation
.hasConnector(connectorId
!) === false) {
927 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
928 a non existing connector id ${connectorId}`,
930 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
933 !isNullOrUndefined(connectorId
) &&
934 isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
!)?.chargingProfiles
)
936 chargingStation
.getConnectorStatus(connectorId
!)!.chargingProfiles
= [];
938 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`,
940 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
942 if (isNullOrUndefined(connectorId
)) {
943 let clearedCP
= false;
944 if (chargingStation
.hasEvses
) {
945 for (const evseStatus
of chargingStation
.evses
.values()) {
946 for (const connectorStatus
of evseStatus
.connectors
.values()) {
947 clearedCP
= OCPP16ServiceUtils
.clearChargingProfiles(
950 connectorStatus
.chargingProfiles
,
955 for (const id
of chargingStation
.connectors
.keys()) {
956 clearedCP
= OCPP16ServiceUtils
.clearChargingProfiles(
959 chargingStation
.getConnectorStatus(id
)?.chargingProfiles
,
964 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
967 return OCPP16Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
970 private async handleRequestChangeAvailability(
971 chargingStation
: ChargingStation
,
972 commandPayload
: OCPP16ChangeAvailabilityRequest
,
973 ): Promise
<OCPP16ChangeAvailabilityResponse
> {
974 const { connectorId
, type } = commandPayload
;
975 if (chargingStation
.hasConnector(connectorId
) === false) {
977 `${chargingStation.logPrefix()} Trying to change the availability of a
978 non existing connector id ${connectorId.toString()}`,
980 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
982 const chargePointStatus
: OCPP16ChargePointStatus
=
983 type === OCPP16AvailabilityType
.Operative
984 ? OCPP16ChargePointStatus
.Available
985 : OCPP16ChargePointStatus
.Unavailable
;
986 if (connectorId
=== 0) {
987 let response
: OCPP16ChangeAvailabilityResponse
;
988 if (chargingStation
.hasEvses
) {
989 for (const evseStatus
of chargingStation
.evses
.values()) {
990 response
= await OCPP16ServiceUtils
.changeAvailability(
992 [...evseStatus
.connectors
.keys()],
998 response
= await OCPP16ServiceUtils
.changeAvailability(
1000 [...chargingStation
.connectors
.keys()],
1008 (chargingStation
.isChargingStationAvailable() === true ||
1009 (chargingStation
.isChargingStationAvailable() === false &&
1010 type === OCPP16AvailabilityType
.Inoperative
))
1012 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
1013 chargingStation
.getConnectorStatus(connectorId
)!.availability
= type;
1014 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
1016 chargingStation
.getConnectorStatus(connectorId
)!.availability
= type;
1017 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1022 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
1024 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
1027 private async handleRequestRemoteStartTransaction(
1028 chargingStation
: ChargingStation
,
1029 commandPayload
: RemoteStartTransactionRequest
,
1030 ): Promise
<GenericResponse
> {
1031 const { connectorId
: transactionConnectorId
, idTag
, chargingProfile
} = commandPayload
;
1032 if (chargingStation
.hasConnector(transactionConnectorId
) === false) {
1033 return this.notifyRemoteStartTransactionRejected(
1035 transactionConnectorId
,
1040 !chargingStation
.isChargingStationAvailable() ||
1041 !chargingStation
.isConnectorAvailable(transactionConnectorId
)
1043 return this.notifyRemoteStartTransactionRejected(
1045 transactionConnectorId
,
1049 const remoteStartTransactionLogMsg
= `
1050 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
1051 chargingStation.stationInfo.chargingStationId
1052 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
1053 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1055 transactionConnectorId
,
1056 OCPP16ChargePointStatus
.Preparing
,
1058 const connectorStatus
= chargingStation
.getConnectorStatus(transactionConnectorId
)!;
1059 // Authorization check required
1061 chargingStation
.getAuthorizeRemoteTxRequests() === true &&
1062 (await OCPP16ServiceUtils
.isIdTagAuthorized(chargingStation
, transactionConnectorId
, idTag
))
1064 // Authorization successful, start transaction
1066 this.setRemoteStartTransactionChargingProfile(
1068 transactionConnectorId
,
1072 connectorStatus
.transactionRemoteStarted
= true;
1075 await chargingStation
.ocppRequestService
.requestHandler
<
1076 OCPP16StartTransactionRequest
,
1077 OCPP16StartTransactionResponse
1078 >(chargingStation
, OCPP16RequestCommand
.START_TRANSACTION
, {
1079 connectorId
: transactionConnectorId
,
1082 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
1084 logger
.debug(remoteStartTransactionLogMsg
);
1085 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
1087 return this.notifyRemoteStartTransactionRejected(
1089 transactionConnectorId
,
1093 return this.notifyRemoteStartTransactionRejected(
1095 transactionConnectorId
,
1099 // No authorization check required, start transaction
1101 this.setRemoteStartTransactionChargingProfile(
1103 transactionConnectorId
,
1107 connectorStatus
.transactionRemoteStarted
= true;
1110 await chargingStation
.ocppRequestService
.requestHandler
<
1111 OCPP16StartTransactionRequest
,
1112 OCPP16StartTransactionResponse
1113 >(chargingStation
, OCPP16RequestCommand
.START_TRANSACTION
, {
1114 connectorId
: transactionConnectorId
,
1117 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
1119 logger
.debug(remoteStartTransactionLogMsg
);
1120 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
1122 return this.notifyRemoteStartTransactionRejected(
1124 transactionConnectorId
,
1128 return this.notifyRemoteStartTransactionRejected(
1130 transactionConnectorId
,
1135 private async notifyRemoteStartTransactionRejected(
1136 chargingStation
: ChargingStation
,
1137 connectorId
: number,
1139 ): Promise
<GenericResponse
> {
1141 chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.Available
1143 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1146 OCPP16ChargePointStatus
.Available
,
1150 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
1151 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
1153 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
1155 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
1158 private setRemoteStartTransactionChargingProfile(
1159 chargingStation
: ChargingStation
,
1160 connectorId
: number,
1161 chargingProfile
: OCPP16ChargingProfile
,
1165 chargingProfile
.chargingProfilePurpose
=== OCPP16ChargingProfilePurposeType
.TX_PROFILE
1167 OCPP16ServiceUtils
.setChargingProfile(chargingStation
, connectorId
, chargingProfile
);
1169 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1170 on connector id ${connectorId}: %j`,
1176 chargingProfile
.chargingProfilePurpose
!== OCPP16ChargingProfilePurposeType
.TX_PROFILE
1179 `${chargingStation.logPrefix()} Not allowed to set ${
1180 chargingProfile.chargingProfilePurpose
1181 } charging profile(s) at remote start transaction`,
1188 private async handleRequestRemoteStopTransaction(
1189 chargingStation
: ChargingStation
,
1190 commandPayload
: RemoteStopTransactionRequest
,
1191 ): Promise
<GenericResponse
> {
1192 const { transactionId
} = commandPayload
;
1193 if (chargingStation
.hasEvses
) {
1194 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1196 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1197 if (connectorStatus
.transactionId
=== transactionId
) {
1198 return OCPP16ServiceUtils
.remoteStopTransaction(chargingStation
, connectorId
);
1204 for (const connectorId
of chargingStation
.connectors
.keys()) {
1207 chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
1209 return OCPP16ServiceUtils
.remoteStopTransaction(chargingStation
, connectorId
);
1214 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
1215 ${transactionId.toString()}`,
1217 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
1220 private handleRequestUpdateFirmware(
1221 chargingStation
: ChargingStation
,
1222 commandPayload
: OCPP16UpdateFirmwareRequest
,
1223 ): OCPP16UpdateFirmwareResponse
{
1225 OCPP16ServiceUtils
.checkFeatureProfile(
1227 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
1228 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
1232 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1233 Cannot simulate firmware update: feature profile not supported`,
1235 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1237 let { retrieveDate
} = commandPayload
;
1239 !isNullOrUndefined(chargingStation
.stationInfo
.firmwareStatus
) &&
1240 chargingStation
.stationInfo
.firmwareStatus
!== OCPP16FirmwareStatus
.Installed
1243 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1244 Cannot simulate firmware update: firmware update is already in progress`,
1246 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1248 retrieveDate
= convertToDate(retrieveDate
)!;
1249 const now
= Date.now();
1250 if (retrieveDate
?.getTime() <= now
) {
1251 this.runInAsyncScope(
1252 this.updateFirmwareSimulation
.bind(this) as (
1253 this: OCPP16IncomingRequestService
,
1258 ).catch(Constants
.EMPTY_FUNCTION
);
1262 this.runInAsyncScope(
1263 this.updateFirmwareSimulation
.bind(this) as (
1264 this: OCPP16IncomingRequestService
,
1269 ).catch(Constants
.EMPTY_FUNCTION
);
1271 retrieveDate
?.getTime() - now
,
1274 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1277 private async updateFirmwareSimulation(
1278 chargingStation
: ChargingStation
,
1282 if (checkChargingStation(chargingStation
, chargingStation
.logPrefix()) === false) {
1285 if (chargingStation
.hasEvses
) {
1286 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1288 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1289 if (connectorStatus
?.transactionStarted
=== false) {
1290 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1293 OCPP16ChargePointStatus
.Unavailable
,
1300 for (const connectorId
of chargingStation
.connectors
.keys()) {
1303 chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== false
1305 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1308 OCPP16ChargePointStatus
.Unavailable
,
1313 await chargingStation
.ocppRequestService
.requestHandler
<
1314 OCPP16FirmwareStatusNotificationRequest
,
1315 OCPP16FirmwareStatusNotificationResponse
1316 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1317 status: OCPP16FirmwareStatus
.Downloading
,
1319 chargingStation
.stationInfo
.firmwareStatus
= OCPP16FirmwareStatus
.Downloading
;
1321 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
===
1322 OCPP16FirmwareStatus
.DownloadFailed
1324 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1325 await chargingStation
.ocppRequestService
.requestHandler
<
1326 OCPP16FirmwareStatusNotificationRequest
,
1327 OCPP16FirmwareStatusNotificationResponse
1328 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1329 status: chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
,
1331 chargingStation
.stationInfo
.firmwareStatus
=
1332 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
;
1335 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1336 await chargingStation
.ocppRequestService
.requestHandler
<
1337 OCPP16FirmwareStatusNotificationRequest
,
1338 OCPP16FirmwareStatusNotificationResponse
1339 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1340 status: OCPP16FirmwareStatus
.Downloaded
,
1342 chargingStation
.stationInfo
.firmwareStatus
= OCPP16FirmwareStatus
.Downloaded
;
1343 let wasTransactionsStarted
= false;
1344 let transactionsStarted
: boolean;
1346 const runningTransactions
= chargingStation
.getNumberOfRunningTransactions();
1347 if (runningTransactions
> 0) {
1348 const waitTime
= secondsToMilliseconds(15);
1350 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1351 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1353 )} before continuing firmware update simulation`,
1355 await sleep(waitTime
);
1356 transactionsStarted
= true;
1357 wasTransactionsStarted
= true;
1359 if (chargingStation
.hasEvses
) {
1360 for (const [evseId
, evseStatus
] of chargingStation
.evses
) {
1362 for (const [connectorId
, connectorStatus
] of evseStatus
.connectors
) {
1363 if (connectorStatus
?.status !== OCPP16ChargePointStatus
.Unavailable
) {
1364 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1367 OCPP16ChargePointStatus
.Unavailable
,
1374 for (const connectorId
of chargingStation
.connectors
.keys()) {
1377 chargingStation
.getConnectorStatus(connectorId
)?.status !==
1378 OCPP16ChargePointStatus
.Unavailable
1380 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
1383 OCPP16ChargePointStatus
.Unavailable
,
1388 transactionsStarted
= false;
1390 } while (transactionsStarted
);
1391 !wasTransactionsStarted
&&
1392 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
))));
1393 if (checkChargingStation(chargingStation
, chargingStation
.logPrefix()) === false) {
1396 await chargingStation
.ocppRequestService
.requestHandler
<
1397 OCPP16FirmwareStatusNotificationRequest
,
1398 OCPP16FirmwareStatusNotificationResponse
1399 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1400 status: OCPP16FirmwareStatus
.Installing
,
1402 chargingStation
.stationInfo
.firmwareStatus
= OCPP16FirmwareStatus
.Installing
;
1404 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
===
1405 OCPP16FirmwareStatus
.InstallationFailed
1407 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1408 await chargingStation
.ocppRequestService
.requestHandler
<
1409 OCPP16FirmwareStatusNotificationRequest
,
1410 OCPP16FirmwareStatusNotificationResponse
1411 >(chargingStation
, OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, {
1412 status: chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
,
1414 chargingStation
.stationInfo
.firmwareStatus
=
1415 chargingStation
.stationInfo
?.firmwareUpgrade
?.failureStatus
;
1418 if (chargingStation
.stationInfo
?.firmwareUpgrade
?.reset
=== true) {
1419 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay
, minDelay
)));
1420 await chargingStation
.reset(OCPP16StopTransactionReason
.REBOOT
);
1424 private async handleRequestGetDiagnostics(
1425 chargingStation
: ChargingStation
,
1426 commandPayload
: GetDiagnosticsRequest
,
1427 ): Promise
<GetDiagnosticsResponse
> {
1429 OCPP16ServiceUtils
.checkFeatureProfile(
1431 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
1432 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1436 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1437 Cannot get diagnostics: feature profile not supported`,
1439 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1441 const { location
} = commandPayload
;
1442 const uri
= new URL(location
);
1443 if (uri
.protocol
.startsWith('ftp:')) {
1444 let ftpClient
: Client
| undefined;
1446 const logFiles
= readdirSync(resolve(dirname(fileURLToPath(import.meta
.url
)), '../'))
1447 .filter((file
) => file
.endsWith('.log'))
1448 .map((file
) => join('./', file
));
1449 const diagnosticsArchive
= `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1450 create({ gzip
: true }, logFiles
).pipe(createWriteStream(diagnosticsArchive
));
1451 ftpClient
= new Client();
1452 const accessResponse
= await ftpClient
.access({
1454 ...(isNotEmptyString(uri
.port
) && { port
: convertToInt(uri
.port
) }),
1455 ...(isNotEmptyString(uri
.username
) && { user
: uri
.username
}),
1456 ...(isNotEmptyString(uri
.password
) && { password
: uri
.password
}),
1458 let uploadResponse
: FTPResponse
| undefined;
1459 if (accessResponse
.code
=== 220) {
1460 ftpClient
.trackProgress((info
) => {
1462 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1464 } bytes transferred from diagnostics archive ${info.name}`,
1466 chargingStation
.ocppRequestService
1468 OCPP16DiagnosticsStatusNotificationRequest
,
1469 OCPP16DiagnosticsStatusNotificationResponse
1470 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1471 status: OCPP16DiagnosticsStatus
.Uploading
,
1475 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1476 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1481 uploadResponse
= await ftpClient
.uploadFrom(
1482 join(resolve(dirname(fileURLToPath(import.meta
.url
)), '../'), diagnosticsArchive
),
1483 `${uri.pathname}${diagnosticsArchive}`,
1485 if (uploadResponse
.code
=== 226) {
1486 await chargingStation
.ocppRequestService
.requestHandler
<
1487 OCPP16DiagnosticsStatusNotificationRequest
,
1488 OCPP16DiagnosticsStatusNotificationResponse
1489 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1490 status: OCPP16DiagnosticsStatus
.Uploaded
,
1495 return { fileName
: diagnosticsArchive
};
1497 throw new OCPPError(
1498 ErrorType
.GENERIC_ERROR
,
1499 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1500 uploadResponse?.code && `|${uploadResponse?.code.toString()}
`
1502 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1505 throw new OCPPError(
1506 ErrorType
.GENERIC_ERROR
,
1507 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1508 uploadResponse?.code && `|${uploadResponse?.code.toString()}
`
1510 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1513 await chargingStation
.ocppRequestService
.requestHandler
<
1514 OCPP16DiagnosticsStatusNotificationRequest
,
1515 OCPP16DiagnosticsStatusNotificationResponse
1516 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1517 status: OCPP16DiagnosticsStatus
.UploadFailed
,
1522 return this.handleIncomingRequestError
<GetDiagnosticsResponse
>(
1524 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
1526 { errorResponse
: OCPP16Constants
.OCPP_RESPONSE_EMPTY
},
1531 `${chargingStation.logPrefix()} Unsupported protocol ${
1533 } to transfer the diagnostic logs archive`,
1535 await chargingStation
.ocppRequestService
.requestHandler
<
1536 OCPP16DiagnosticsStatusNotificationRequest
,
1537 OCPP16DiagnosticsStatusNotificationResponse
1538 >(chargingStation
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
1539 status: OCPP16DiagnosticsStatus
.UploadFailed
,
1541 return OCPP16Constants
.OCPP_RESPONSE_EMPTY
;
1545 private handleRequestTriggerMessage(
1546 chargingStation
: ChargingStation
,
1547 commandPayload
: OCPP16TriggerMessageRequest
,
1548 ): OCPP16TriggerMessageResponse
{
1549 const { requestedMessage
, connectorId
} = commandPayload
;
1551 !OCPP16ServiceUtils
.checkFeatureProfile(
1553 OCPP16SupportedFeatureProfiles
.RemoteTrigger
,
1554 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1556 !OCPP16ServiceUtils
.isMessageTriggerSupported(chargingStation
, requestedMessage
)
1558 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1561 !OCPP16ServiceUtils
.isConnectorIdValid(
1563 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1567 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
;
1570 switch (requestedMessage
) {
1571 case OCPP16MessageTrigger
.BootNotification
:
1573 chargingStation
.ocppRequestService
1574 .requestHandler
<OCPP16BootNotificationRequest
, OCPP16BootNotificationResponse
>(
1576 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
1577 chargingStation
.bootNotificationRequest
,
1578 { skipBufferingOnError
: true, triggerMessage
: true },
1580 .then((response
) => {
1581 chargingStation
.bootNotificationResponse
= response
;
1583 .catch(Constants
.EMPTY_FUNCTION
);
1584 }, OCPP16Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1585 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1586 case OCPP16MessageTrigger
.Heartbeat
:
1588 chargingStation
.ocppRequestService
1589 .requestHandler
<OCPP16HeartbeatRequest
, OCPP16HeartbeatResponse
>(
1591 OCPP16RequestCommand
.HEARTBEAT
,
1594 triggerMessage
: true,
1597 .catch(Constants
.EMPTY_FUNCTION
);
1598 }, OCPP16Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1599 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1600 case OCPP16MessageTrigger
.StatusNotification
:
1602 if (!isNullOrUndefined(connectorId
)) {
1603 chargingStation
.ocppRequestService
1604 .requestHandler
<OCPP16StatusNotificationRequest
, OCPP16StatusNotificationResponse
>(
1606 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1609 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1610 status: chargingStation
.getConnectorStatus(connectorId
!)?.status,
1613 triggerMessage
: true,
1616 .catch(Constants
.EMPTY_FUNCTION
);
1618 // eslint-disable-next-line no-lonely-if
1619 if (chargingStation
.hasEvses
) {
1620 for (const evseStatus
of chargingStation
.evses
.values()) {
1621 for (const [id
, connectorStatus
] of evseStatus
.connectors
) {
1622 chargingStation
.ocppRequestService
1624 OCPP16StatusNotificationRequest
,
1625 OCPP16StatusNotificationResponse
1628 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1631 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1632 status: connectorStatus
.status,
1635 triggerMessage
: true,
1638 .catch(Constants
.EMPTY_FUNCTION
);
1642 for (const id
of chargingStation
.connectors
.keys()) {
1643 chargingStation
.ocppRequestService
1645 OCPP16StatusNotificationRequest
,
1646 OCPP16StatusNotificationResponse
1649 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1652 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1653 status: chargingStation
.getConnectorStatus(id
)?.status,
1656 triggerMessage
: true,
1659 .catch(Constants
.EMPTY_FUNCTION
);
1663 }, OCPP16Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1664 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1666 return OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1669 return this.handleIncomingRequestError
<OCPP16TriggerMessageResponse
>(
1671 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1673 { errorResponse
: OCPP16Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
},
1678 private handleRequestDataTransfer(
1679 chargingStation
: ChargingStation
,
1680 commandPayload
: OCPP16DataTransferRequest
,
1681 ): OCPP16DataTransferResponse
{
1682 const { vendorId
} = commandPayload
;
1684 if (Object.values(OCPP16DataTransferVendorId
).includes(vendorId
)) {
1685 return OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
;
1687 return OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
;
1689 return this.handleIncomingRequestError
<OCPP16DataTransferResponse
>(
1691 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
1693 { errorResponse
: OCPP16Constants
.OCPP_DATA_TRANSFER_RESPONSE_REJECTED
},
1698 private async handleRequestReserveNow(
1699 chargingStation
: ChargingStation
,
1700 commandPayload
: OCPP16ReserveNowRequest
,
1701 ): Promise
<OCPP16ReserveNowResponse
> {
1703 !OCPP16ServiceUtils
.checkFeatureProfile(
1705 OCPP16SupportedFeatureProfiles
.Reservation
,
1706 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
1709 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1711 const { reservationId
, idTag
, connectorId
} = commandPayload
;
1712 let response
: OCPP16ReserveNowResponse
;
1714 if (connectorId
> 0 && !chargingStation
.isConnectorAvailable(connectorId
)) {
1715 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1717 if (connectorId
=== 0 && !chargingStation
.getReserveConnectorZeroSupported()) {
1718 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1720 if (!(await OCPP16ServiceUtils
.isIdTagAuthorized(chargingStation
, connectorId
, idTag
))) {
1721 return OCPP16Constants
.OCPP_RESERVATION_RESPONSE_REJECTED
;
1723 await removeExpiredReservations(chargingStation
);
1724 switch (chargingStation
.getConnectorStatus(connectorId
)!.status) {
1725 case OCPP16ChargePointStatus
.Faulted
:
1726 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_FAULTED
;
1728 case OCPP16ChargePointStatus
.Preparing
:
1729 case OCPP16ChargePointStatus
.Charging
:
1730 case OCPP16ChargePointStatus
.SuspendedEV
:
1731 case OCPP16ChargePointStatus
.SuspendedEVSE
:
1732 case OCPP16ChargePointStatus
.Finishing
:
1733 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
;
1735 case OCPP16ChargePointStatus
.Unavailable
:
1736 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
;
1738 case OCPP16ChargePointStatus
.Reserved
:
1739 if (!chargingStation
.isConnectorReservable(reservationId
, idTag
, connectorId
)) {
1740 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
;
1743 // eslint-disable-next-line no-fallthrough
1745 if (!chargingStation
.isConnectorReservable(reservationId
, idTag
)) {
1746 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_OCCUPIED
;
1749 await chargingStation
.addReservation({
1750 id
: commandPayload
.reservationId
,
1753 response
= OCPP16Constants
.OCPP_RESERVATION_RESPONSE_ACCEPTED
;
1758 chargingStation
.getConnectorStatus(connectorId
)!.status = OCPP16ChargePointStatus
.Available
;
1759 return this.handleIncomingRequestError
<OCPP16ReserveNowResponse
>(
1761 OCPP16IncomingRequestCommand
.RESERVE_NOW
,
1763 { errorResponse
: OCPP16Constants
.OCPP_RESERVATION_RESPONSE_FAULTED
},
1768 private async handleRequestCancelReservation(
1769 chargingStation
: ChargingStation
,
1770 commandPayload
: OCPP16CancelReservationRequest
,
1771 ): Promise
<GenericResponse
> {
1773 !OCPP16ServiceUtils
.checkFeatureProfile(
1775 OCPP16SupportedFeatureProfiles
.Reservation
,
1776 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
1779 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
;
1782 const { reservationId
} = commandPayload
;
1783 const reservation
= chargingStation
.getReservationBy('reservationId', reservationId
);
1784 if (isUndefined(reservation
)) {
1786 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1787 does not exist on charging station`,
1789 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
;
1791 await chargingStation
.removeReservation(
1793 ReservationTerminationReason
.RESERVATION_CANCELED
,
1795 return OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
;
1797 return this.handleIncomingRequestError
<GenericResponse
>(
1799 OCPP16IncomingRequestCommand
.CANCEL_RESERVATION
,
1801 { errorResponse
: OCPP16Constants
.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
},