1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
4 ChangeAvailabilityRequest
,
5 ChangeConfigurationRequest
,
6 ClearChargingProfileRequest
,
7 GetConfigurationRequest
,
10 OCPP16AvailabilityType
,
11 OCPP16IncomingRequestCommand
,
13 OCPP16TriggerMessageRequest
,
14 RemoteStartTransactionRequest
,
15 RemoteStopTransactionRequest
,
17 SetChargingProfileRequest
,
18 UnlockConnectorRequest
,
19 } from
'../../../types/ocpp/1.6/Requests';
21 ChangeAvailabilityResponse
,
22 ChangeConfigurationResponse
,
23 ClearChargingProfileResponse
,
24 GetConfigurationResponse
,
25 GetDiagnosticsResponse
,
26 OCPP16TriggerMessageResponse
,
27 SetChargingProfileResponse
,
28 UnlockConnectorResponse
,
29 } from
'../../../types/ocpp/1.6/Responses';
31 ChargingProfilePurposeType
,
32 OCPP16ChargingProfile
,
33 } from
'../../../types/ocpp/1.6/ChargingProfile';
34 import { Client
, FTPResponse
} from
'basic-ftp';
36 OCPP16AuthorizationStatus
,
37 OCPP16AuthorizeResponse
,
38 OCPP16StartTransactionResponse
,
39 OCPP16StopTransactionReason
,
40 OCPP16StopTransactionResponse
,
41 } from
'../../../types/ocpp/1.6/Transaction';
43 import type ChargingStation from
'../../ChargingStation';
44 import Constants from
'../../../utils/Constants';
45 import { DefaultResponse
} from
'../../../types/ocpp/Responses';
46 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
47 import { IncomingRequestHandler
} from
'../../../types/ocpp/Requests';
48 import { JsonType
} from
'../../../types/JsonType';
49 import { OCPP16ChargePointErrorCode
} from
'../../../types/ocpp/1.6/ChargePointErrorCode';
50 import { OCPP16ChargePointStatus
} from
'../../../types/ocpp/1.6/ChargePointStatus';
51 import { OCPP16DiagnosticsStatus
} from
'../../../types/ocpp/1.6/DiagnosticsStatus';
52 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
53 import { OCPP16StandardParametersKey
} from
'../../../types/ocpp/1.6/Configuration';
54 import { OCPPConfigurationKey
} from
'../../../types/ocpp/Configuration';
55 import OCPPError from
'../../../exception/OCPPError';
56 import OCPPIncomingRequestService from
'../OCPPIncomingRequestService';
57 import { URL
} from
'url';
58 import Utils from
'../../../utils/Utils';
60 import logger from
'../../../utils/Logger';
61 import path from
'path';
62 import tar from
'tar';
64 const moduleName
= 'OCPP16IncomingRequestService';
66 export default class OCPP16IncomingRequestService
extends OCPPIncomingRequestService
{
67 private incomingRequestHandlers
: Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>;
69 public constructor(chargingStation
: ChargingStation
) {
70 if (new.target
?.name
=== moduleName
) {
71 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
73 super(chargingStation
);
74 this.incomingRequestHandlers
= new Map
<OCPP16IncomingRequestCommand
, IncomingRequestHandler
>([
75 [OCPP16IncomingRequestCommand
.RESET
, this.handleRequestReset
.bind(this)],
76 [OCPP16IncomingRequestCommand
.CLEAR_CACHE
, this.handleRequestClearCache
.bind(this)],
77 [OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
, this.handleRequestUnlockConnector
.bind(this)],
79 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
80 this.handleRequestGetConfiguration
.bind(this),
83 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
84 this.handleRequestChangeConfiguration
.bind(this),
87 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
88 this.handleRequestSetChargingProfile
.bind(this),
91 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
92 this.handleRequestClearChargingProfile
.bind(this),
95 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
96 this.handleRequestChangeAvailability
.bind(this),
99 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
100 this.handleRequestRemoteStartTransaction
.bind(this),
103 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
104 this.handleRequestRemoteStopTransaction
.bind(this),
106 [OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
, this.handleRequestGetDiagnostics
.bind(this)],
107 [OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
, this.handleRequestTriggerMessage
.bind(this)],
111 public async handleRequest(
113 commandName
: OCPP16IncomingRequestCommand
,
114 commandPayload
: JsonType
116 let result
: JsonType
;
118 this.chargingStation
.getOcppStrictCompliance() &&
119 this.chargingStation
.isInPendingState() &&
120 (commandName
=== OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
||
121 commandName
=== OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
)
124 ErrorType
.SECURITY_ERROR
,
125 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
129 )} while the charging station is in pending state on the central server`,
134 this.chargingStation
.isRegistered() ||
135 (!this.chargingStation
.getOcppStrictCompliance() && this.chargingStation
.isInUnknownState())
137 if (this.incomingRequestHandlers
.has(commandName
)) {
139 // Call the method to build the result
140 result
= await this.incomingRequestHandlers
.get(commandName
)(commandPayload
);
143 logger
.error(this.chargingStation
.logPrefix() + ' Handle request error: %j', error
);
149 ErrorType
.NOT_IMPLEMENTED
,
150 `${commandName} is not implemented to handle request payload ${JSON.stringify(
160 ErrorType
.SECURITY_ERROR
,
161 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
165 )} while the charging station is not registered on the central server.`,
169 // Send the built result
170 await this.chargingStation
.ocppRequestService
.sendResult(messageId
, result
, commandName
);
173 // Simulate charging station restart
174 private handleRequestReset(commandPayload
: ResetRequest
): DefaultResponse
{
175 // eslint-disable-next-line @typescript-eslint/no-misused-promises
176 setImmediate(async (): Promise
<void> => {
177 await this.chargingStation
.stop(
178 (commandPayload
.type + 'Reset') as OCPP16StopTransactionReason
180 await Utils
.sleep(this.chargingStation
.stationInfo
.resetTime
);
181 this.chargingStation
.start();
184 `${this.chargingStation.logPrefix()} ${
186 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
187 this.chargingStation.stationInfo.resetTime
190 return Constants
.OCPP_RESPONSE_ACCEPTED
;
193 private handleRequestClearCache(): DefaultResponse
{
194 return Constants
.OCPP_RESPONSE_ACCEPTED
;
197 private async handleRequestUnlockConnector(
198 commandPayload
: UnlockConnectorRequest
199 ): Promise
<UnlockConnectorResponse
> {
200 const connectorId
= commandPayload
.connectorId
;
201 if (connectorId
=== 0) {
203 this.chargingStation
.logPrefix() + ' Trying to unlock connector ' + connectorId
.toString()
205 return Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
;
207 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
208 const transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
210 this.chargingStation
.getBeginEndMeterValues() &&
211 this.chargingStation
.getOcppStrictCompliance() &&
212 !this.chargingStation
.getOutOfOrderEndMeterValues()
214 // FIXME: Implement OCPP version agnostic helpers
215 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
216 this.chargingStation
,
218 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
220 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
221 OCPP16RequestCommand
.METER_VALUES
,
225 meterValue
: transactionEndMeterValue
,
229 const stopResponse
= (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
230 OCPP16RequestCommand
.STOP_TRANSACTION
,
234 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
235 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
236 reason
: OCPP16StopTransactionReason
.UNLOCK_COMMAND
,
238 )) as OCPP16StopTransactionResponse
;
239 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
240 return Constants
.OCPP_RESPONSE_UNLOCKED
;
242 return Constants
.OCPP_RESPONSE_UNLOCK_FAILED
;
244 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
245 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
248 status: OCPP16ChargePointStatus
.AVAILABLE
,
249 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
252 this.chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
253 return Constants
.OCPP_RESPONSE_UNLOCKED
;
256 private handleRequestGetConfiguration(
257 commandPayload
: GetConfigurationRequest
258 ): GetConfigurationResponse
{
259 const configurationKey
: OCPPConfigurationKey
[] = [];
260 const unknownKey
: string[] = [];
261 if (Utils
.isEmptyArray(commandPayload
.key
)) {
262 for (const configuration
of this.chargingStation
.configuration
.configurationKey
) {
263 if (Utils
.isUndefined(configuration
.visible
)) {
264 configuration
.visible
= true;
266 if (!configuration
.visible
) {
269 configurationKey
.push({
270 key
: configuration
.key
,
271 readonly: configuration
.readonly,
272 value
: configuration
.value
,
276 for (const key
of commandPayload
.key
) {
277 const keyFound
= this.chargingStation
.getConfigurationKey(key
);
279 if (Utils
.isUndefined(keyFound
.visible
)) {
280 keyFound
.visible
= true;
282 if (!keyFound
.visible
) {
285 configurationKey
.push({
287 readonly: keyFound
.readonly,
288 value
: keyFound
.value
,
291 unknownKey
.push(key
);
301 private handleRequestChangeConfiguration(
302 commandPayload
: ChangeConfigurationRequest
303 ): ChangeConfigurationResponse
{
304 // JSON request fields type sanity check
305 if (!Utils
.isString(commandPayload
.key
)) {
307 `${this.chargingStation.logPrefix()} ${
308 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
309 } request key field is not a string:`,
313 if (!Utils
.isString(commandPayload
.value
)) {
315 `${this.chargingStation.logPrefix()} ${
316 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
317 } request value field is not a string:`,
321 const keyToChange
= this.chargingStation
.getConfigurationKey(commandPayload
.key
, true);
323 return Constants
.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
;
324 } else if (keyToChange
&& keyToChange
.readonly) {
325 return Constants
.OCPP_CONFIGURATION_RESPONSE_REJECTED
;
326 } else if (keyToChange
&& !keyToChange
.readonly) {
327 let valueChanged
= false;
328 if (keyToChange
.value
!== commandPayload
.value
) {
329 this.chargingStation
.setConfigurationKeyValue(
331 commandPayload
.value
,
336 let triggerHeartbeatRestart
= false;
337 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartBeatInterval
&& valueChanged
) {
338 this.chargingStation
.setConfigurationKeyValue(
339 OCPP16StandardParametersKey
.HeartbeatInterval
,
342 triggerHeartbeatRestart
= true;
344 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartbeatInterval
&& valueChanged
) {
345 this.chargingStation
.setConfigurationKeyValue(
346 OCPP16StandardParametersKey
.HeartBeatInterval
,
349 triggerHeartbeatRestart
= true;
351 if (triggerHeartbeatRestart
) {
352 this.chargingStation
.restartHeartbeat();
354 if (keyToChange
.key
=== OCPP16StandardParametersKey
.WebSocketPingInterval
&& valueChanged
) {
355 this.chargingStation
.restartWebSocketPing();
357 if (keyToChange
.reboot
) {
358 return Constants
.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
;
360 return Constants
.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
;
364 private handleRequestSetChargingProfile(
365 commandPayload
: SetChargingProfileRequest
366 ): SetChargingProfileResponse
{
367 if (!this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)) {
369 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
370 commandPayload.connectorId
373 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
376 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
377 ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
378 commandPayload
.connectorId
!== 0
380 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
383 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
384 ChargingProfilePurposeType
.TX_PROFILE
&&
385 (commandPayload
.connectorId
=== 0 ||
386 !this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)?.transactionStarted
)
388 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
390 this.chargingStation
.setChargingProfile(
391 commandPayload
.connectorId
,
392 commandPayload
.csChargingProfiles
395 `${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`,
396 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
398 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
401 private handleRequestClearChargingProfile(
402 commandPayload
: ClearChargingProfileRequest
403 ): ClearChargingProfileResponse
{
404 const connectorStatus
= this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
);
405 if (!connectorStatus
) {
407 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
408 commandPayload.connectorId
411 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
413 if (commandPayload
.connectorId
&& !Utils
.isEmptyArray(connectorStatus
.chargingProfiles
)) {
414 connectorStatus
.chargingProfiles
= [];
416 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
417 connectorStatus
.chargingProfiles
419 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
421 if (!commandPayload
.connectorId
) {
422 let clearedCP
= false;
423 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
425 !Utils
.isEmptyArray(this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)
428 .getConnectorStatus(connectorId
)
429 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
430 let clearCurrentCP
= false;
431 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
432 clearCurrentCP
= true;
435 !commandPayload
.chargingProfilePurpose
&&
436 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
438 clearCurrentCP
= true;
441 !chargingProfile
.stackLevel
&&
442 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
444 clearCurrentCP
= true;
447 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
448 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
450 clearCurrentCP
= true;
452 if (clearCurrentCP
) {
453 connectorStatus
.chargingProfiles
[index
] = {} as OCPP16ChargingProfile
;
455 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
456 connectorStatus
.chargingProfiles
464 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
467 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
470 private async handleRequestChangeAvailability(
471 commandPayload
: ChangeAvailabilityRequest
472 ): Promise
<ChangeAvailabilityResponse
> {
473 const connectorId
: number = commandPayload
.connectorId
;
474 if (!this.chargingStation
.getConnectorStatus(connectorId
)) {
476 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
478 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
480 const chargePointStatus
: OCPP16ChargePointStatus
=
481 commandPayload
.type === OCPP16AvailabilityType
.OPERATIVE
482 ? OCPP16ChargePointStatus
.AVAILABLE
483 : OCPP16ChargePointStatus
.UNAVAILABLE
;
484 if (connectorId
=== 0) {
485 let response
: ChangeAvailabilityResponse
= Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
486 for (const id
of this.chargingStation
.connectors
.keys()) {
487 if (this.chargingStation
.getConnectorStatus(id
)?.transactionStarted
) {
488 response
= Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
490 this.chargingStation
.getConnectorStatus(id
).availability
= commandPayload
.type;
491 if (response
=== Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
492 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
493 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
496 status: chargePointStatus
,
497 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
500 this.chargingStation
.getConnectorStatus(id
).status = chargePointStatus
;
506 (this.chargingStation
.getConnectorStatus(0).availability
===
507 OCPP16AvailabilityType
.OPERATIVE
||
508 (this.chargingStation
.getConnectorStatus(0).availability
===
509 OCPP16AvailabilityType
.INOPERATIVE
&&
510 commandPayload
.type === OCPP16AvailabilityType
.INOPERATIVE
))
512 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
513 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
514 return Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
516 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
517 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
518 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
519 { connectorId
, status: chargePointStatus
, errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
}
521 this.chargingStation
.getConnectorStatus(connectorId
).status = chargePointStatus
;
522 return Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
524 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
527 private async handleRequestRemoteStartTransaction(
528 commandPayload
: RemoteStartTransactionRequest
529 ): Promise
<DefaultResponse
> {
530 const transactionConnectorId
= commandPayload
.connectorId
;
531 const connectorStatus
= this.chargingStation
.getConnectorStatus(transactionConnectorId
);
532 if (transactionConnectorId
) {
533 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
534 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
536 connectorId
: transactionConnectorId
,
537 status: OCPP16ChargePointStatus
.PREPARING
,
538 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
541 connectorStatus
.status = OCPP16ChargePointStatus
.PREPARING
;
542 if (this.chargingStation
.isChargingStationAvailable() && connectorStatus
) {
543 // Check if authorized
544 if (this.chargingStation
.getAuthorizeRemoteTxRequests()) {
545 let authorized
= false;
547 this.chargingStation
.getLocalAuthListEnabled() &&
548 this.chargingStation
.hasAuthorizedTags() &&
549 this.chargingStation
.authorizedTags
.find((value
) => value
=== commandPayload
.idTag
)
551 connectorStatus
.localAuthorizeIdTag
= commandPayload
.idTag
;
552 connectorStatus
.idTagLocalAuthorized
= true;
554 } else if (this.chargingStation
.getMayAuthorizeAtRemoteStart()) {
555 connectorStatus
.authorizeIdTag
= commandPayload
.idTag
;
556 const authorizeResponse
: OCPP16AuthorizeResponse
=
557 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
558 OCPP16RequestCommand
.AUTHORIZE
,
560 idTag
: commandPayload
.idTag
,
562 )) as OCPP16AuthorizeResponse
;
563 if (authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
568 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
572 // Authorization successful, start transaction
574 this.setRemoteStartTransactionChargingProfile(
575 transactionConnectorId
,
576 commandPayload
.chargingProfile
579 connectorStatus
.transactionRemoteStarted
= true;
582 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
583 OCPP16RequestCommand
.START_TRANSACTION
,
585 connectorId
: transactionConnectorId
,
586 idTag
: commandPayload
.idTag
,
588 )) as OCPP16StartTransactionResponse
589 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
592 this.chargingStation
.logPrefix() +
593 ' Transaction remotely STARTED on ' +
594 this.chargingStation
.stationInfo
.chargingStationId
+
596 transactionConnectorId
.toString() +
600 return Constants
.OCPP_RESPONSE_ACCEPTED
;
602 return this.notifyRemoteStartTransactionRejected(
603 transactionConnectorId
,
607 return this.notifyRemoteStartTransactionRejected(
608 transactionConnectorId
,
612 return this.notifyRemoteStartTransactionRejected(
613 transactionConnectorId
,
617 // No authorization check required, start transaction
619 this.setRemoteStartTransactionChargingProfile(
620 transactionConnectorId
,
621 commandPayload
.chargingProfile
624 connectorStatus
.transactionRemoteStarted
= true;
627 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
628 OCPP16RequestCommand
.START_TRANSACTION
,
630 connectorId
: transactionConnectorId
,
631 idTag
: commandPayload
.idTag
,
633 )) as OCPP16StartTransactionResponse
634 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
637 this.chargingStation
.logPrefix() +
638 ' Transaction remotely STARTED on ' +
639 this.chargingStation
.stationInfo
.chargingStationId
+
641 transactionConnectorId
.toString() +
645 return Constants
.OCPP_RESPONSE_ACCEPTED
;
647 return this.notifyRemoteStartTransactionRejected(
648 transactionConnectorId
,
652 return this.notifyRemoteStartTransactionRejected(
653 transactionConnectorId
,
657 return this.notifyRemoteStartTransactionRejected(
658 transactionConnectorId
,
662 return this.notifyRemoteStartTransactionRejected(transactionConnectorId
, commandPayload
.idTag
);
665 private async notifyRemoteStartTransactionRejected(
668 ): Promise
<DefaultResponse
> {
670 this.chargingStation
.getConnectorStatus(connectorId
).status !==
671 OCPP16ChargePointStatus
.AVAILABLE
673 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
674 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
677 status: OCPP16ChargePointStatus
.AVAILABLE
,
678 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
681 this.chargingStation
.getConnectorStatus(connectorId
).status =
682 OCPP16ChargePointStatus
.AVAILABLE
;
685 this.chargingStation
.logPrefix() +
686 ' Remote starting transaction REJECTED on connector Id ' +
687 connectorId
.toString() +
691 this.chargingStation
.getConnectorStatus(connectorId
).availability
+
693 this.chargingStation
.getConnectorStatus(connectorId
).status
695 return Constants
.OCPP_RESPONSE_REJECTED
;
698 private setRemoteStartTransactionChargingProfile(
700 cp
: OCPP16ChargingProfile
702 if (cp
&& cp
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
) {
703 this.chargingStation
.setChargingProfile(connectorId
, cp
);
705 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`,
706 this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
709 } else if (cp
&& cp
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
) {
711 `${this.chargingStation.logPrefix()} Not allowed to set ${
712 cp.chargingProfilePurpose
713 } charging profile(s) at remote start transaction`
721 private async handleRequestRemoteStopTransaction(
722 commandPayload
: RemoteStopTransactionRequest
723 ): Promise
<DefaultResponse
> {
724 const transactionId
= commandPayload
.transactionId
;
725 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
728 this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
730 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
731 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
734 status: OCPP16ChargePointStatus
.FINISHING
,
735 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
738 this.chargingStation
.getConnectorStatus(connectorId
).status =
739 OCPP16ChargePointStatus
.FINISHING
;
741 this.chargingStation
.getBeginEndMeterValues() &&
742 this.chargingStation
.getOcppStrictCompliance() &&
743 !this.chargingStation
.getOutOfOrderEndMeterValues()
745 // FIXME: Implement OCPP version agnostic helpers
746 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
747 this.chargingStation
,
749 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
751 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
752 OCPP16RequestCommand
.METER_VALUES
,
756 meterValue
: transactionEndMeterValue
,
760 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
761 OCPP16RequestCommand
.STOP_TRANSACTION
,
765 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
766 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
769 return Constants
.OCPP_RESPONSE_ACCEPTED
;
773 this.chargingStation
.logPrefix() +
774 ' Trying to remote stop a non existing transaction ' +
775 transactionId
.toString()
777 return Constants
.OCPP_RESPONSE_REJECTED
;
780 private async handleRequestGetDiagnostics(
781 commandPayload
: GetDiagnosticsRequest
782 ): Promise
<GetDiagnosticsResponse
> {
784 this.chargingStation
.logPrefix() +
786 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
+
787 ' request received: %j',
790 const uri
= new URL(commandPayload
.location
);
791 if (uri
.protocol
.startsWith('ftp:')) {
792 let ftpClient
: Client
;
795 .readdirSync(path
.resolve(__dirname
, '../../../../'))
796 .filter((file
) => file
.endsWith('.log'))
797 .map((file
) => path
.join('./', file
));
798 const diagnosticsArchive
=
799 this.chargingStation
.stationInfo
.chargingStationId
+ '_logs.tar.gz';
800 tar
.create({ gzip
: true }, logFiles
).pipe(fs
.createWriteStream(diagnosticsArchive
));
801 ftpClient
= new Client();
802 const accessResponse
= await ftpClient
.access({
804 ...(!Utils
.isEmptyString(uri
.port
) && { port
: Utils
.convertToInt(uri
.port
) }),
805 ...(!Utils
.isEmptyString(uri
.username
) && { user
: uri
.username
}),
806 ...(!Utils
.isEmptyString(uri
.password
) && { password
: uri
.password
}),
808 let uploadResponse
: FTPResponse
;
809 if (accessResponse
.code
=== 220) {
810 // eslint-disable-next-line @typescript-eslint/no-misused-promises
811 ftpClient
.trackProgress(async (info
) => {
813 `${this.chargingStation.logPrefix()} ${
815 } bytes transferred from diagnostics archive ${info.name}`
817 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
818 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
820 status: OCPP16DiagnosticsStatus
.Uploading
,
824 uploadResponse
= await ftpClient
.uploadFrom(
825 path
.join(path
.resolve(__dirname
, '../../../../'), diagnosticsArchive
),
826 uri
.pathname
+ diagnosticsArchive
828 if (uploadResponse
.code
=== 226) {
829 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
830 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
832 status: OCPP16DiagnosticsStatus
.Uploaded
,
838 return { fileName
: diagnosticsArchive
};
841 ErrorType
.GENERIC_ERROR
,
842 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
843 uploadResponse?.code && '|' + uploadResponse?.code.toString()
845 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
849 ErrorType
.GENERIC_ERROR
,
850 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
851 uploadResponse?.code && '|' + uploadResponse?.code.toString()
853 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
856 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
857 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
859 status: OCPP16DiagnosticsStatus
.UploadFailed
,
865 return this.handleIncomingRequestError(
866 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
868 { errorResponse
: Constants
.OCPP_RESPONSE_EMPTY
}
873 `${this.chargingStation.logPrefix()} Unsupported protocol ${
875 } to transfer the diagnostic logs archive`
877 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
878 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
880 status: OCPP16DiagnosticsStatus
.UploadFailed
,
883 return Constants
.OCPP_RESPONSE_EMPTY
;
887 private handleRequestTriggerMessage(
888 commandPayload
: OCPP16TriggerMessageRequest
889 ): OCPP16TriggerMessageResponse
{
891 switch (commandPayload
.requestedMessage
) {
892 case MessageTrigger
.BootNotification
:
894 this.chargingStation
.ocppRequestService
896 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
899 this.chargingStation
.getBootNotificationRequest().chargePointModel
,
901 this.chargingStation
.getBootNotificationRequest().chargePointVendor
,
902 chargeBoxSerialNumber
:
903 this.chargingStation
.getBootNotificationRequest().chargeBoxSerialNumber
,
905 this.chargingStation
.getBootNotificationRequest().firmwareVersion
,
906 chargePointSerialNumber
:
907 this.chargingStation
.getBootNotificationRequest().chargePointSerialNumber
,
908 iccid
: this.chargingStation
.getBootNotificationRequest().iccid
,
909 imsi
: this.chargingStation
.getBootNotificationRequest().imsi
,
911 this.chargingStation
.getBootNotificationRequest().meterSerialNumber
,
912 meterType
: this.chargingStation
.getBootNotificationRequest().meterType
,
914 { skipBufferingOnError
: true, triggerMessage
: true }
917 /* This is intentional */
919 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
920 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
921 case MessageTrigger
.Heartbeat
:
923 this.chargingStation
.ocppRequestService
924 .sendMessageHandler(OCPP16RequestCommand
.HEARTBEAT
, null, { triggerMessage
: true })
926 /* This is intentional */
928 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
929 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
931 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
934 return this.handleIncomingRequestError(
935 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
937 { errorResponse
: Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
}