1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
4 ChangeAvailabilityRequest
,
5 ChangeConfigurationRequest
,
6 ClearChargingProfileRequest
,
7 DiagnosticsStatusNotificationRequest
,
8 GetConfigurationRequest
,
11 OCPP16AvailabilityType
,
12 OCPP16BootNotificationRequest
,
13 OCPP16HeartbeatRequest
,
14 OCPP16IncomingRequestCommand
,
16 OCPP16StatusNotificationRequest
,
17 OCPP16TriggerMessageRequest
,
18 RemoteStartTransactionRequest
,
19 RemoteStopTransactionRequest
,
21 SetChargingProfileRequest
,
22 UnlockConnectorRequest
,
23 } from
'../../../types/ocpp/1.6/Requests';
25 ChangeAvailabilityResponse
,
26 ChangeConfigurationResponse
,
27 ClearChargingProfileResponse
,
28 DiagnosticsStatusNotificationResponse
,
29 GetConfigurationResponse
,
30 GetDiagnosticsResponse
,
31 OCPP16BootNotificationResponse
,
32 OCPP16HeartbeatResponse
,
33 OCPP16StatusNotificationResponse
,
34 OCPP16TriggerMessageResponse
,
35 SetChargingProfileResponse
,
36 UnlockConnectorResponse
,
37 } from
'../../../types/ocpp/1.6/Responses';
39 ChargingProfilePurposeType
,
40 OCPP16ChargingProfile
,
41 } from
'../../../types/ocpp/1.6/ChargingProfile';
42 import { Client
, FTPResponse
} from
'basic-ftp';
44 OCPP16AuthorizationStatus
,
45 OCPP16AuthorizeRequest
,
46 OCPP16AuthorizeResponse
,
47 OCPP16StartTransactionRequest
,
48 OCPP16StartTransactionResponse
,
49 OCPP16StopTransactionReason
,
50 OCPP16StopTransactionRequest
,
51 OCPP16StopTransactionResponse
,
52 } from
'../../../types/ocpp/1.6/Transaction';
54 OCPP16MeterValuesRequest
,
55 OCPP16MeterValuesResponse
,
56 } from
'../../../types/ocpp/1.6/MeterValues';
58 OCPP16StandardParametersKey
,
59 OCPP16SupportedFeatureProfiles
,
60 } from
'../../../types/ocpp/1.6/Configuration';
62 import type ChargingStation from
'../../ChargingStation';
63 import Constants from
'../../../utils/Constants';
64 import { DefaultResponse
} from
'../../../types/ocpp/Responses';
65 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
66 import { IncomingRequestHandler
} from
'../../../types/ocpp/Requests';
67 import { JsonObject
} from
'../../../types/JsonType';
68 import { OCPP16ChargePointErrorCode
} from
'../../../types/ocpp/1.6/ChargePointErrorCode';
69 import { OCPP16ChargePointStatus
} from
'../../../types/ocpp/1.6/ChargePointStatus';
70 import { OCPP16DiagnosticsStatus
} from
'../../../types/ocpp/1.6/DiagnosticsStatus';
71 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
72 import { OCPPConfigurationKey
} from
'../../../types/ocpp/Configuration';
73 import OCPPError from
'../../../exception/OCPPError';
74 import OCPPIncomingRequestService from
'../OCPPIncomingRequestService';
75 import { URL
} from
'url';
76 import Utils from
'../../../utils/Utils';
78 import logger from
'../../../utils/Logger';
79 import path from
'path';
80 import tar from
'tar';
82 const moduleName
= 'OCPP16IncomingRequestService';
84 export default class OCPP16IncomingRequestService
extends OCPPIncomingRequestService
{
85 private incomingRequestHandlers
: Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>;
87 public constructor(chargingStation
: ChargingStation
) {
88 if (new.target
?.name
=== moduleName
) {
89 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
91 super(chargingStation
);
92 this.incomingRequestHandlers
= new Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>([
93 [OCPP16IncomingRequestCommand
.RESET
, this.handleRequestReset
.bind(this)],
94 [OCPP16IncomingRequestCommand
.CLEAR_CACHE
, this.handleRequestClearCache
.bind(this)],
95 [OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
, this.handleRequestUnlockConnector
.bind(this)],
97 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
98 this.handleRequestGetConfiguration
.bind(this),
101 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
102 this.handleRequestChangeConfiguration
.bind(this),
105 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
106 this.handleRequestSetChargingProfile
.bind(this),
109 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
110 this.handleRequestClearChargingProfile
.bind(this),
113 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
114 this.handleRequestChangeAvailability
.bind(this),
117 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
118 this.handleRequestRemoteStartTransaction
.bind(this),
121 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
122 this.handleRequestRemoteStopTransaction
.bind(this),
124 [OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
, this.handleRequestGetDiagnostics
.bind(this)],
125 [OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
, this.handleRequestTriggerMessage
.bind(this)],
129 public async incomingRequestHandler(
131 commandName
: OCPP16IncomingRequestCommand
,
132 commandPayload
: JsonObject
134 let response
: JsonObject
;
136 this.chargingStation
.getOcppStrictCompliance() &&
137 this.chargingStation
.isInPendingState() &&
138 (commandName
=== OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
||
139 commandName
=== OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
)
142 ErrorType
.SECURITY_ERROR
,
143 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
147 )} while the charging station is in pending state on the central server`,
152 this.chargingStation
.isRegistered() ||
153 (!this.chargingStation
.getOcppStrictCompliance() && this.chargingStation
.isInUnknownState())
155 if (this.incomingRequestHandlers
.has(commandName
)) {
157 // Call the method to build the response
158 response
= await this.incomingRequestHandlers
.get(commandName
)(commandPayload
);
161 logger
.error(this.chargingStation
.logPrefix() + ' Handle request error: %j', error
);
167 ErrorType
.NOT_IMPLEMENTED
,
168 `${commandName} is not implemented to handle request payload ${JSON.stringify(
178 ErrorType
.SECURITY_ERROR
,
179 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
183 )} while the charging station is not registered on the central server.`,
187 // Send the built response
188 await this.chargingStation
.ocppRequestService
.sendResponse(messageId
, response
, commandName
);
191 // Simulate charging station restart
192 private handleRequestReset(commandPayload
: ResetRequest
): DefaultResponse
{
193 // eslint-disable-next-line @typescript-eslint/no-misused-promises
194 setImmediate(async (): Promise
<void> => {
195 await this.chargingStation
.reset(
196 (commandPayload
.type + 'Reset') as OCPP16StopTransactionReason
200 `${this.chargingStation.logPrefix()} ${
202 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
203 this.chargingStation.stationInfo.resetTime
206 return Constants
.OCPP_RESPONSE_ACCEPTED
;
209 private handleRequestClearCache(): DefaultResponse
{
210 return Constants
.OCPP_RESPONSE_ACCEPTED
;
213 private async handleRequestUnlockConnector(
214 commandPayload
: UnlockConnectorRequest
215 ): Promise
<UnlockConnectorResponse
> {
216 const connectorId
= commandPayload
.connectorId
;
217 if (connectorId
=== 0) {
219 this.chargingStation
.logPrefix() + ' Trying to unlock connector ' + connectorId
.toString()
221 return Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
;
223 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
224 const transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
226 this.chargingStation
.getBeginEndMeterValues() &&
227 this.chargingStation
.getOcppStrictCompliance() &&
228 !this.chargingStation
.getOutOfOrderEndMeterValues()
230 // FIXME: Implement OCPP version agnostic helpers
231 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
232 this.chargingStation
,
234 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
236 await this.chargingStation
.ocppRequestService
.requestHandler
<
237 OCPP16MeterValuesRequest
,
238 OCPP16MeterValuesResponse
239 >(OCPP16RequestCommand
.METER_VALUES
, {
242 meterValue
: transactionEndMeterValue
,
245 const stopResponse
= await this.chargingStation
.ocppRequestService
.requestHandler
<
246 OCPP16StopTransactionRequest
,
247 OCPP16StopTransactionResponse
248 >(OCPP16RequestCommand
.STOP_TRANSACTION
, {
250 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
251 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
252 reason
: OCPP16StopTransactionReason
.UNLOCK_COMMAND
,
254 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
255 return Constants
.OCPP_RESPONSE_UNLOCKED
;
257 return Constants
.OCPP_RESPONSE_UNLOCK_FAILED
;
259 await this.chargingStation
.ocppRequestService
.requestHandler
<
260 OCPP16StatusNotificationRequest
,
261 OCPP16StatusNotificationResponse
262 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
264 status: OCPP16ChargePointStatus
.AVAILABLE
,
265 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
267 this.chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
268 return Constants
.OCPP_RESPONSE_UNLOCKED
;
271 private handleRequestGetConfiguration(
272 commandPayload
: GetConfigurationRequest
273 ): GetConfigurationResponse
{
274 const configurationKey
: OCPPConfigurationKey
[] = [];
275 const unknownKey
: string[] = [];
276 if (Utils
.isEmptyArray(commandPayload
.key
)) {
277 for (const configuration
of this.chargingStation
.ocppConfiguration
.configurationKey
) {
278 if (Utils
.isUndefined(configuration
.visible
)) {
279 configuration
.visible
= true;
281 if (!configuration
.visible
) {
284 configurationKey
.push({
285 key
: configuration
.key
,
286 readonly: configuration
.readonly,
287 value
: configuration
.value
,
291 for (const key
of commandPayload
.key
) {
292 const keyFound
= this.chargingStation
.getConfigurationKey(key
);
294 if (Utils
.isUndefined(keyFound
.visible
)) {
295 keyFound
.visible
= true;
297 if (!keyFound
.visible
) {
300 configurationKey
.push({
302 readonly: keyFound
.readonly,
303 value
: keyFound
.value
,
306 unknownKey
.push(key
);
316 private handleRequestChangeConfiguration(
317 commandPayload
: ChangeConfigurationRequest
318 ): ChangeConfigurationResponse
{
319 // JSON request fields type sanity check
320 if (!Utils
.isString(commandPayload
.key
)) {
322 `${this.chargingStation.logPrefix()} ${
323 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
324 } request key field is not a string:`,
328 if (!Utils
.isString(commandPayload
.value
)) {
330 `${this.chargingStation.logPrefix()} ${
331 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
332 } request value field is not a string:`,
336 const keyToChange
= this.chargingStation
.getConfigurationKey(commandPayload
.key
, true);
338 return Constants
.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
;
339 } else if (keyToChange
&& keyToChange
.readonly) {
340 return Constants
.OCPP_CONFIGURATION_RESPONSE_REJECTED
;
341 } else if (keyToChange
&& !keyToChange
.readonly) {
342 let valueChanged
= false;
343 if (keyToChange
.value
!== commandPayload
.value
) {
344 this.chargingStation
.setConfigurationKeyValue(
346 commandPayload
.value
,
351 let triggerHeartbeatRestart
= false;
352 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartBeatInterval
&& valueChanged
) {
353 this.chargingStation
.setConfigurationKeyValue(
354 OCPP16StandardParametersKey
.HeartbeatInterval
,
357 triggerHeartbeatRestart
= true;
359 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartbeatInterval
&& valueChanged
) {
360 this.chargingStation
.setConfigurationKeyValue(
361 OCPP16StandardParametersKey
.HeartBeatInterval
,
364 triggerHeartbeatRestart
= true;
366 if (triggerHeartbeatRestart
) {
367 this.chargingStation
.restartHeartbeat();
369 if (keyToChange
.key
=== OCPP16StandardParametersKey
.WebSocketPingInterval
&& valueChanged
) {
370 this.chargingStation
.restartWebSocketPing();
372 if (keyToChange
.reboot
) {
373 return Constants
.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
;
375 return Constants
.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
;
379 private handleRequestSetChargingProfile(
380 commandPayload
: SetChargingProfileRequest
381 ): SetChargingProfileResponse
{
383 !OCPP16ServiceUtils
.checkFeatureProfile(
384 this.chargingStation
,
385 OCPP16SupportedFeatureProfiles
.SmartCharging
,
386 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
389 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
;
391 if (!this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)) {
393 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
394 commandPayload.connectorId
397 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
400 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
401 ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
402 commandPayload
.connectorId
!== 0
404 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
407 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
408 ChargingProfilePurposeType
.TX_PROFILE
&&
409 (commandPayload
.connectorId
=== 0 ||
410 !this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)?.transactionStarted
)
412 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
414 this.chargingStation
.setChargingProfile(
415 commandPayload
.connectorId
,
416 commandPayload
.csChargingProfiles
419 `${this.chargingStation.logPrefix()} Charging profile(s) set on connector id ${
420 commandPayload.connectorId
421 }, dump their stack: %j`,
422 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
424 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
427 private handleRequestClearChargingProfile(
428 commandPayload
: ClearChargingProfileRequest
429 ): ClearChargingProfileResponse
{
431 !OCPP16ServiceUtils
.checkFeatureProfile(
432 this.chargingStation
,
433 OCPP16SupportedFeatureProfiles
.SmartCharging
,
434 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
437 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
439 const connectorStatus
= this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
);
440 if (!connectorStatus
) {
442 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
443 commandPayload.connectorId
446 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
448 if (commandPayload
.connectorId
&& !Utils
.isEmptyArray(connectorStatus
.chargingProfiles
)) {
449 connectorStatus
.chargingProfiles
= [];
451 `${this.chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
452 commandPayload.connectorId
453 }, dump their stack: %j`,
454 connectorStatus
.chargingProfiles
456 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
458 if (!commandPayload
.connectorId
) {
459 let clearedCP
= false;
460 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
462 !Utils
.isEmptyArray(this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)
465 .getConnectorStatus(connectorId
)
466 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
467 let clearCurrentCP
= false;
468 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
469 clearCurrentCP
= true;
472 !commandPayload
.chargingProfilePurpose
&&
473 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
475 clearCurrentCP
= true;
478 !chargingProfile
.stackLevel
&&
479 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
481 clearCurrentCP
= true;
484 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
485 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
487 clearCurrentCP
= true;
489 if (clearCurrentCP
) {
490 connectorStatus
.chargingProfiles
[index
] = {} as OCPP16ChargingProfile
;
492 `${this.chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
493 commandPayload.connectorId
494 }, dump their stack: %j`,
495 connectorStatus
.chargingProfiles
503 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
506 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
509 private async handleRequestChangeAvailability(
510 commandPayload
: ChangeAvailabilityRequest
511 ): Promise
<ChangeAvailabilityResponse
> {
512 const connectorId
: number = commandPayload
.connectorId
;
513 if (!this.chargingStation
.getConnectorStatus(connectorId
)) {
515 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
517 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
519 const chargePointStatus
: OCPP16ChargePointStatus
=
520 commandPayload
.type === OCPP16AvailabilityType
.OPERATIVE
521 ? OCPP16ChargePointStatus
.AVAILABLE
522 : OCPP16ChargePointStatus
.UNAVAILABLE
;
523 if (connectorId
=== 0) {
524 let response
: ChangeAvailabilityResponse
= Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
525 for (const id
of this.chargingStation
.connectors
.keys()) {
526 if (this.chargingStation
.getConnectorStatus(id
)?.transactionStarted
) {
527 response
= Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
529 this.chargingStation
.getConnectorStatus(id
).availability
= commandPayload
.type;
530 if (response
=== Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
531 await this.chargingStation
.ocppRequestService
.requestHandler
<
532 OCPP16StatusNotificationRequest
,
533 OCPP16StatusNotificationResponse
534 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
536 status: chargePointStatus
,
537 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
539 this.chargingStation
.getConnectorStatus(id
).status = chargePointStatus
;
545 (this.chargingStation
.getConnectorStatus(0).availability
===
546 OCPP16AvailabilityType
.OPERATIVE
||
547 (this.chargingStation
.getConnectorStatus(0).availability
===
548 OCPP16AvailabilityType
.INOPERATIVE
&&
549 commandPayload
.type === OCPP16AvailabilityType
.INOPERATIVE
))
551 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
552 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
553 return Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
555 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
556 await this.chargingStation
.ocppRequestService
.requestHandler
<
557 OCPP16StatusNotificationRequest
,
558 OCPP16StatusNotificationResponse
559 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
561 status: chargePointStatus
,
562 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
564 this.chargingStation
.getConnectorStatus(connectorId
).status = chargePointStatus
;
565 return Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
567 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
570 private async handleRequestRemoteStartTransaction(
571 commandPayload
: RemoteStartTransactionRequest
572 ): Promise
<DefaultResponse
> {
573 const transactionConnectorId
= commandPayload
.connectorId
;
574 const connectorStatus
= this.chargingStation
.getConnectorStatus(transactionConnectorId
);
575 if (transactionConnectorId
) {
576 await this.chargingStation
.ocppRequestService
.requestHandler
<
577 OCPP16StatusNotificationRequest
,
578 OCPP16StatusNotificationResponse
579 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
580 connectorId
: transactionConnectorId
,
581 status: OCPP16ChargePointStatus
.PREPARING
,
582 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
584 connectorStatus
.status = OCPP16ChargePointStatus
.PREPARING
;
585 if (this.chargingStation
.isChargingStationAvailable() && connectorStatus
) {
586 // Check if authorized
587 if (this.chargingStation
.getAuthorizeRemoteTxRequests()) {
588 let authorized
= false;
590 this.chargingStation
.getLocalAuthListEnabled() &&
591 this.chargingStation
.hasAuthorizedTags() &&
592 this.chargingStation
.authorizedTags
.find((value
) => value
=== commandPayload
.idTag
)
594 connectorStatus
.localAuthorizeIdTag
= commandPayload
.idTag
;
595 connectorStatus
.idTagLocalAuthorized
= true;
597 } else if (this.chargingStation
.getMayAuthorizeAtRemoteStart()) {
598 connectorStatus
.authorizeIdTag
= commandPayload
.idTag
;
599 const authorizeResponse
: OCPP16AuthorizeResponse
=
600 await this.chargingStation
.ocppRequestService
.requestHandler
<
601 OCPP16AuthorizeRequest
,
602 OCPP16AuthorizeResponse
603 >(OCPP16RequestCommand
.AUTHORIZE
, {
604 idTag
: commandPayload
.idTag
,
606 if (authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
611 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
615 // Authorization successful, start transaction
617 this.setRemoteStartTransactionChargingProfile(
618 transactionConnectorId
,
619 commandPayload
.chargingProfile
622 connectorStatus
.transactionRemoteStarted
= true;
625 await this.chargingStation
.ocppRequestService
.requestHandler
<
626 OCPP16StartTransactionRequest
,
627 OCPP16StartTransactionResponse
628 >(OCPP16RequestCommand
.START_TRANSACTION
, {
629 connectorId
: transactionConnectorId
,
630 idTag
: commandPayload
.idTag
,
632 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
635 this.chargingStation
.logPrefix() +
636 ' Transaction remotely STARTED on ' +
637 this.chargingStation
.stationInfo
.chargingStationId
+
639 transactionConnectorId
.toString() +
643 return Constants
.OCPP_RESPONSE_ACCEPTED
;
645 return this.notifyRemoteStartTransactionRejected(
646 transactionConnectorId
,
650 return this.notifyRemoteStartTransactionRejected(
651 transactionConnectorId
,
655 return this.notifyRemoteStartTransactionRejected(
656 transactionConnectorId
,
660 // No authorization check required, start transaction
662 this.setRemoteStartTransactionChargingProfile(
663 transactionConnectorId
,
664 commandPayload
.chargingProfile
667 connectorStatus
.transactionRemoteStarted
= true;
670 await this.chargingStation
.ocppRequestService
.requestHandler
<
671 OCPP16StartTransactionRequest
,
672 OCPP16StartTransactionResponse
673 >(OCPP16RequestCommand
.START_TRANSACTION
, {
674 connectorId
: transactionConnectorId
,
675 idTag
: commandPayload
.idTag
,
677 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
680 this.chargingStation
.logPrefix() +
681 ' Transaction remotely STARTED on ' +
682 this.chargingStation
.stationInfo
.chargingStationId
+
684 transactionConnectorId
.toString() +
688 return Constants
.OCPP_RESPONSE_ACCEPTED
;
690 return this.notifyRemoteStartTransactionRejected(
691 transactionConnectorId
,
695 return this.notifyRemoteStartTransactionRejected(
696 transactionConnectorId
,
700 return this.notifyRemoteStartTransactionRejected(
701 transactionConnectorId
,
705 return this.notifyRemoteStartTransactionRejected(transactionConnectorId
, commandPayload
.idTag
);
708 private async notifyRemoteStartTransactionRejected(
711 ): Promise
<DefaultResponse
> {
713 this.chargingStation
.getConnectorStatus(connectorId
).status !==
714 OCPP16ChargePointStatus
.AVAILABLE
716 await this.chargingStation
.ocppRequestService
.requestHandler
<
717 OCPP16StatusNotificationRequest
,
718 OCPP16StatusNotificationResponse
719 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
721 status: OCPP16ChargePointStatus
.AVAILABLE
,
722 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
724 this.chargingStation
.getConnectorStatus(connectorId
).status =
725 OCPP16ChargePointStatus
.AVAILABLE
;
728 this.chargingStation
.logPrefix() +
729 ' Remote starting transaction REJECTED on connector Id ' +
730 connectorId
.toString() +
734 this.chargingStation
.getConnectorStatus(connectorId
).availability
+
736 this.chargingStation
.getConnectorStatus(connectorId
).status
738 return Constants
.OCPP_RESPONSE_REJECTED
;
741 private setRemoteStartTransactionChargingProfile(
743 cp
: OCPP16ChargingProfile
745 if (cp
&& cp
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
) {
746 this.chargingStation
.setChargingProfile(connectorId
, cp
);
748 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
749 this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
752 } else if (cp
&& cp
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
) {
754 `${this.chargingStation.logPrefix()} Not allowed to set ${
755 cp.chargingProfilePurpose
756 } charging profile(s) at remote start transaction`
764 private async handleRequestRemoteStopTransaction(
765 commandPayload
: RemoteStopTransactionRequest
766 ): Promise
<DefaultResponse
> {
767 const transactionId
= commandPayload
.transactionId
;
768 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
771 this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
773 await this.chargingStation
.ocppRequestService
.requestHandler
<
774 OCPP16StatusNotificationRequest
,
775 OCPP16StatusNotificationResponse
776 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
778 status: OCPP16ChargePointStatus
.FINISHING
,
779 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
781 this.chargingStation
.getConnectorStatus(connectorId
).status =
782 OCPP16ChargePointStatus
.FINISHING
;
784 this.chargingStation
.getBeginEndMeterValues() &&
785 this.chargingStation
.getOcppStrictCompliance() &&
786 !this.chargingStation
.getOutOfOrderEndMeterValues()
788 // FIXME: Implement OCPP version agnostic helpers
789 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
790 this.chargingStation
,
792 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
794 await this.chargingStation
.ocppRequestService
.requestHandler
<
795 OCPP16MeterValuesRequest
,
796 OCPP16MeterValuesResponse
797 >(OCPP16RequestCommand
.METER_VALUES
, {
800 meterValue
: transactionEndMeterValue
,
803 await this.chargingStation
.ocppRequestService
.requestHandler
<
804 OCPP16StopTransactionRequest
,
805 OCPP16StopTransactionResponse
806 >(OCPP16RequestCommand
.STOP_TRANSACTION
, {
809 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
810 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
812 return Constants
.OCPP_RESPONSE_ACCEPTED
;
816 this.chargingStation
.logPrefix() +
817 ' Trying to remote stop a non existing transaction ' +
818 transactionId
.toString()
820 return Constants
.OCPP_RESPONSE_REJECTED
;
823 private async handleRequestGetDiagnostics(
824 commandPayload
: GetDiagnosticsRequest
825 ): Promise
<GetDiagnosticsResponse
> {
827 !OCPP16ServiceUtils
.checkFeatureProfile(
828 this.chargingStation
,
829 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
830 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
833 return Constants
.OCPP_RESPONSE_EMPTY
;
836 this.chargingStation
.logPrefix() +
838 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
+
839 ' request received: %j',
842 const uri
= new URL(commandPayload
.location
);
843 if (uri
.protocol
.startsWith('ftp:')) {
844 let ftpClient
: Client
;
847 .readdirSync(path
.resolve(__dirname
, '../../../../'))
848 .filter((file
) => file
.endsWith('.log'))
849 .map((file
) => path
.join('./', file
));
850 const diagnosticsArchive
=
851 this.chargingStation
.stationInfo
.chargingStationId
+ '_logs.tar.gz';
852 tar
.create({ gzip
: true }, logFiles
).pipe(fs
.createWriteStream(diagnosticsArchive
));
853 ftpClient
= new Client();
854 const accessResponse
= await ftpClient
.access({
856 ...(!Utils
.isEmptyString(uri
.port
) && { port
: Utils
.convertToInt(uri
.port
) }),
857 ...(!Utils
.isEmptyString(uri
.username
) && { user
: uri
.username
}),
858 ...(!Utils
.isEmptyString(uri
.password
) && { password
: uri
.password
}),
860 let uploadResponse
: FTPResponse
;
861 if (accessResponse
.code
=== 220) {
862 // eslint-disable-next-line @typescript-eslint/no-misused-promises
863 ftpClient
.trackProgress(async (info
) => {
865 `${this.chargingStation.logPrefix()} ${
867 } bytes transferred from diagnostics archive ${info.name}`
869 await this.chargingStation
.ocppRequestService
.requestHandler
<
870 DiagnosticsStatusNotificationRequest
,
871 DiagnosticsStatusNotificationResponse
872 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
873 status: OCPP16DiagnosticsStatus
.Uploading
,
876 uploadResponse
= await ftpClient
.uploadFrom(
877 path
.join(path
.resolve(__dirname
, '../../../../'), diagnosticsArchive
),
878 uri
.pathname
+ diagnosticsArchive
880 if (uploadResponse
.code
=== 226) {
881 await this.chargingStation
.ocppRequestService
.requestHandler
<
882 DiagnosticsStatusNotificationRequest
,
883 DiagnosticsStatusNotificationResponse
884 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
885 status: OCPP16DiagnosticsStatus
.Uploaded
,
890 return { fileName
: diagnosticsArchive
};
893 ErrorType
.GENERIC_ERROR
,
894 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
895 uploadResponse?.code && '|' + uploadResponse?.code.toString()
897 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
901 ErrorType
.GENERIC_ERROR
,
902 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
903 uploadResponse?.code && '|' + uploadResponse?.code.toString()
905 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
908 await this.chargingStation
.ocppRequestService
.requestHandler
<
909 DiagnosticsStatusNotificationRequest
,
910 DiagnosticsStatusNotificationResponse
911 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
912 status: OCPP16DiagnosticsStatus
.UploadFailed
,
917 return this.handleIncomingRequestError(
918 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
920 { errorResponse
: Constants
.OCPP_RESPONSE_EMPTY
}
925 `${this.chargingStation.logPrefix()} Unsupported protocol ${
927 } to transfer the diagnostic logs archive`
929 await this.chargingStation
.ocppRequestService
.requestHandler
<
930 DiagnosticsStatusNotificationRequest
,
931 DiagnosticsStatusNotificationResponse
932 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
933 status: OCPP16DiagnosticsStatus
.UploadFailed
,
935 return Constants
.OCPP_RESPONSE_EMPTY
;
939 private handleRequestTriggerMessage(
940 commandPayload
: OCPP16TriggerMessageRequest
941 ): OCPP16TriggerMessageResponse
{
943 !OCPP16ServiceUtils
.checkFeatureProfile(
944 this.chargingStation
,
945 OCPP16SupportedFeatureProfiles
.RemoteTrigger
,
946 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
949 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
951 // TODO: factor out the check on connector id
952 if (commandPayload
?.connectorId
< 0) {
954 `${this.chargingStation.logPrefix()} ${
955 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
956 } incoming request received with invalid connectorId ${commandPayload.connectorId}`
958 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
;
961 switch (commandPayload
.requestedMessage
) {
962 case MessageTrigger
.BootNotification
:
964 this.chargingStation
.ocppRequestService
965 .requestHandler
<OCPP16BootNotificationRequest
, OCPP16BootNotificationResponse
>(
966 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
969 this.chargingStation
.getBootNotificationRequest().chargePointModel
,
971 this.chargingStation
.getBootNotificationRequest().chargePointVendor
,
972 chargeBoxSerialNumber
:
973 this.chargingStation
.getBootNotificationRequest().chargeBoxSerialNumber
,
975 this.chargingStation
.getBootNotificationRequest().firmwareVersion
,
976 chargePointSerialNumber
:
977 this.chargingStation
.getBootNotificationRequest().chargePointSerialNumber
,
978 iccid
: this.chargingStation
.getBootNotificationRequest().iccid
,
979 imsi
: this.chargingStation
.getBootNotificationRequest().imsi
,
981 this.chargingStation
.getBootNotificationRequest().meterSerialNumber
,
982 meterType
: this.chargingStation
.getBootNotificationRequest().meterType
,
984 { skipBufferingOnError
: true, triggerMessage
: true }
987 this.chargingStation
.bootNotificationResponse
= value
;
990 /* This is intentional */
992 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
993 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
994 case MessageTrigger
.Heartbeat
:
996 this.chargingStation
.ocppRequestService
997 .requestHandler
<OCPP16HeartbeatRequest
, OCPP16HeartbeatResponse
>(
998 OCPP16RequestCommand
.HEARTBEAT
,
1001 triggerMessage
: true,
1005 /* This is intentional */
1007 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1008 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1009 case MessageTrigger
.StatusNotification
:
1011 if (commandPayload
?.connectorId
) {
1012 this.chargingStation
.ocppRequestService
1013 .requestHandler
<OCPP16StatusNotificationRequest
, OCPP16StatusNotificationResponse
>(
1014 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1016 connectorId
: commandPayload
.connectorId
,
1017 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1018 status: this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)
1022 triggerMessage
: true,
1026 /* This is intentional */
1029 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
1030 this.chargingStation
.ocppRequestService
1032 OCPP16StatusNotificationRequest
,
1033 OCPP16StatusNotificationResponse
1035 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
1038 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
1039 status: this.chargingStation
.getConnectorStatus(connectorId
).status,
1042 triggerMessage
: true,
1046 /* This is intentional */
1050 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1051 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1053 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1056 return this.handleIncomingRequestError(
1057 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1059 { errorResponse
: Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
}