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 const keyIndex
= this.chargingStation
.configuration
.configurationKey
.indexOf(keyToChange
);
328 let valueChanged
= false;
330 this.chargingStation
.configuration
.configurationKey
[keyIndex
].value
!== commandPayload
.value
332 this.chargingStation
.configuration
.configurationKey
[keyIndex
].value
= commandPayload
.value
;
335 let triggerHeartbeatRestart
= false;
336 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartBeatInterval
&& valueChanged
) {
337 this.chargingStation
.setConfigurationKeyValue(
338 OCPP16StandardParametersKey
.HeartbeatInterval
,
341 triggerHeartbeatRestart
= true;
343 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartbeatInterval
&& valueChanged
) {
344 this.chargingStation
.setConfigurationKeyValue(
345 OCPP16StandardParametersKey
.HeartBeatInterval
,
348 triggerHeartbeatRestart
= true;
350 if (triggerHeartbeatRestart
) {
351 this.chargingStation
.restartHeartbeat();
353 if (keyToChange
.key
=== OCPP16StandardParametersKey
.WebSocketPingInterval
&& valueChanged
) {
354 this.chargingStation
.restartWebSocketPing();
356 if (keyToChange
.reboot
) {
357 return Constants
.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
;
359 return Constants
.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
;
363 private handleRequestSetChargingProfile(
364 commandPayload
: SetChargingProfileRequest
365 ): SetChargingProfileResponse
{
366 if (!this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)) {
368 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
369 commandPayload.connectorId
372 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
375 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
376 ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
377 commandPayload
.connectorId
!== 0
379 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
382 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
383 ChargingProfilePurposeType
.TX_PROFILE
&&
384 (commandPayload
.connectorId
=== 0 ||
385 !this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)?.transactionStarted
)
387 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
389 this.chargingStation
.setChargingProfile(
390 commandPayload
.connectorId
,
391 commandPayload
.csChargingProfiles
394 `${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`,
395 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
397 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
400 private handleRequestClearChargingProfile(
401 commandPayload
: ClearChargingProfileRequest
402 ): ClearChargingProfileResponse
{
403 if (!this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)) {
405 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
406 commandPayload.connectorId
409 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
412 commandPayload
.connectorId
&&
414 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
417 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
= [];
419 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
420 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
422 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
424 if (!commandPayload
.connectorId
) {
425 let clearedCP
= false;
426 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
428 !Utils
.isEmptyArray(this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)
431 .getConnectorStatus(connectorId
)
432 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
433 let clearCurrentCP
= false;
434 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
435 clearCurrentCP
= true;
438 !commandPayload
.chargingProfilePurpose
&&
439 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
441 clearCurrentCP
= true;
444 !chargingProfile
.stackLevel
&&
445 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
447 clearCurrentCP
= true;
450 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
451 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
453 clearCurrentCP
= true;
455 if (clearCurrentCP
) {
456 this.chargingStation
.getConnectorStatus(
457 commandPayload
.connectorId
458 ).chargingProfiles
[index
] = {} as OCPP16ChargingProfile
;
460 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
461 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)
470 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
473 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
476 private async handleRequestChangeAvailability(
477 commandPayload
: ChangeAvailabilityRequest
478 ): Promise
<ChangeAvailabilityResponse
> {
479 const connectorId
: number = commandPayload
.connectorId
;
480 if (!this.chargingStation
.getConnectorStatus(connectorId
)) {
482 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
484 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
486 const chargePointStatus
: OCPP16ChargePointStatus
=
487 commandPayload
.type === OCPP16AvailabilityType
.OPERATIVE
488 ? OCPP16ChargePointStatus
.AVAILABLE
489 : OCPP16ChargePointStatus
.UNAVAILABLE
;
490 if (connectorId
=== 0) {
491 let response
: ChangeAvailabilityResponse
= Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
492 for (const id
of this.chargingStation
.connectors
.keys()) {
493 if (this.chargingStation
.getConnectorStatus(id
)?.transactionStarted
) {
494 response
= Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
496 this.chargingStation
.getConnectorStatus(id
).availability
= commandPayload
.type;
497 if (response
=== Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
498 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
499 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
502 status: chargePointStatus
,
503 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
506 this.chargingStation
.getConnectorStatus(id
).status = chargePointStatus
;
512 (this.chargingStation
.getConnectorStatus(0).availability
===
513 OCPP16AvailabilityType
.OPERATIVE
||
514 (this.chargingStation
.getConnectorStatus(0).availability
===
515 OCPP16AvailabilityType
.INOPERATIVE
&&
516 commandPayload
.type === OCPP16AvailabilityType
.INOPERATIVE
))
518 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
519 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
520 return Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
522 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
523 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
524 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
525 { connectorId
, status: chargePointStatus
, errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
}
527 this.chargingStation
.getConnectorStatus(connectorId
).status = chargePointStatus
;
528 return Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
530 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
533 private async handleRequestRemoteStartTransaction(
534 commandPayload
: RemoteStartTransactionRequest
535 ): Promise
<DefaultResponse
> {
536 const transactionConnectorId
: number = commandPayload
.connectorId
;
537 if (transactionConnectorId
) {
538 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
539 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
541 connectorId
: transactionConnectorId
,
542 status: OCPP16ChargePointStatus
.PREPARING
,
543 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
546 this.chargingStation
.getConnectorStatus(transactionConnectorId
).status =
547 OCPP16ChargePointStatus
.PREPARING
;
549 this.chargingStation
.isChargingStationAvailable() &&
550 this.chargingStation
.isConnectorAvailable(transactionConnectorId
)
552 // Check if authorized
553 if (this.chargingStation
.getAuthorizeRemoteTxRequests()) {
554 let authorized
= false;
556 this.chargingStation
.getLocalAuthListEnabled() &&
557 this.chargingStation
.hasAuthorizedTags() &&
558 this.chargingStation
.authorizedTags
.find((value
) => value
=== commandPayload
.idTag
)
560 this.chargingStation
.getConnectorStatus(transactionConnectorId
).localAuthorizeIdTag
=
561 commandPayload
.idTag
;
562 this.chargingStation
.getConnectorStatus(transactionConnectorId
).idTagLocalAuthorized
=
565 } else if (this.chargingStation
.getMayAuthorizeAtRemoteStart()) {
566 this.chargingStation
.getConnectorStatus(transactionConnectorId
).authorizeIdTag
=
567 commandPayload
.idTag
;
568 const authorizeResponse
: OCPP16AuthorizeResponse
=
569 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
570 OCPP16RequestCommand
.AUTHORIZE
,
572 idTag
: commandPayload
.idTag
,
574 )) as OCPP16AuthorizeResponse
;
575 if (authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
580 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
584 // Authorization successful, start transaction
586 this.setRemoteStartTransactionChargingProfile(
587 transactionConnectorId
,
588 commandPayload
.chargingProfile
591 this.chargingStation
.getConnectorStatus(
592 transactionConnectorId
593 ).transactionRemoteStarted
= true;
596 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
597 OCPP16RequestCommand
.START_TRANSACTION
,
599 connectorId
: transactionConnectorId
,
600 idTag
: commandPayload
.idTag
,
602 )) as OCPP16StartTransactionResponse
603 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
606 this.chargingStation
.logPrefix() +
607 ' Transaction remotely STARTED on ' +
608 this.chargingStation
.stationInfo
.chargingStationId
+
610 transactionConnectorId
.toString() +
614 return Constants
.OCPP_RESPONSE_ACCEPTED
;
616 return this.notifyRemoteStartTransactionRejected(
617 transactionConnectorId
,
621 return this.notifyRemoteStartTransactionRejected(
622 transactionConnectorId
,
626 return this.notifyRemoteStartTransactionRejected(
627 transactionConnectorId
,
631 // No authorization check required, start transaction
633 this.setRemoteStartTransactionChargingProfile(
634 transactionConnectorId
,
635 commandPayload
.chargingProfile
638 this.chargingStation
.getConnectorStatus(transactionConnectorId
).transactionRemoteStarted
=
642 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
643 OCPP16RequestCommand
.START_TRANSACTION
,
645 connectorId
: transactionConnectorId
,
646 idTag
: commandPayload
.idTag
,
648 )) as OCPP16StartTransactionResponse
649 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
652 this.chargingStation
.logPrefix() +
653 ' Transaction remotely STARTED on ' +
654 this.chargingStation
.stationInfo
.chargingStationId
+
656 transactionConnectorId
.toString() +
660 return Constants
.OCPP_RESPONSE_ACCEPTED
;
662 return this.notifyRemoteStartTransactionRejected(
663 transactionConnectorId
,
667 return this.notifyRemoteStartTransactionRejected(
668 transactionConnectorId
,
672 return this.notifyRemoteStartTransactionRejected(
673 transactionConnectorId
,
677 return this.notifyRemoteStartTransactionRejected(transactionConnectorId
, commandPayload
.idTag
);
680 private async notifyRemoteStartTransactionRejected(
683 ): Promise
<DefaultResponse
> {
685 this.chargingStation
.getConnectorStatus(connectorId
).status !==
686 OCPP16ChargePointStatus
.AVAILABLE
688 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
689 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
692 status: OCPP16ChargePointStatus
.AVAILABLE
,
693 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
696 this.chargingStation
.getConnectorStatus(connectorId
).status =
697 OCPP16ChargePointStatus
.AVAILABLE
;
700 this.chargingStation
.logPrefix() +
701 ' Remote starting transaction REJECTED on connector Id ' +
702 connectorId
.toString() +
706 this.chargingStation
.getConnectorStatus(connectorId
).availability
+
708 this.chargingStation
.getConnectorStatus(connectorId
).status
710 return Constants
.OCPP_RESPONSE_REJECTED
;
713 private setRemoteStartTransactionChargingProfile(
715 cp
: OCPP16ChargingProfile
717 if (cp
&& cp
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
) {
718 this.chargingStation
.setChargingProfile(connectorId
, cp
);
720 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`,
721 this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
724 } else if (cp
&& cp
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
) {
726 `${this.chargingStation.logPrefix()} Not allowed to set ${
727 cp.chargingProfilePurpose
728 } charging profile(s) at remote start transaction`
736 private async handleRequestRemoteStopTransaction(
737 commandPayload
: RemoteStopTransactionRequest
738 ): Promise
<DefaultResponse
> {
739 const transactionId
= commandPayload
.transactionId
;
740 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
743 this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
745 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
746 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
749 status: OCPP16ChargePointStatus
.FINISHING
,
750 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
753 this.chargingStation
.getConnectorStatus(connectorId
).status =
754 OCPP16ChargePointStatus
.FINISHING
;
756 this.chargingStation
.getBeginEndMeterValues() &&
757 this.chargingStation
.getOcppStrictCompliance() &&
758 !this.chargingStation
.getOutOfOrderEndMeterValues()
760 // FIXME: Implement OCPP version agnostic helpers
761 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
762 this.chargingStation
,
764 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
766 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
767 OCPP16RequestCommand
.METER_VALUES
,
771 meterValue
: transactionEndMeterValue
,
775 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
776 OCPP16RequestCommand
.STOP_TRANSACTION
,
780 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
781 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
784 return Constants
.OCPP_RESPONSE_ACCEPTED
;
788 this.chargingStation
.logPrefix() +
789 ' Trying to remote stop a non existing transaction ' +
790 transactionId
.toString()
792 return Constants
.OCPP_RESPONSE_REJECTED
;
795 private async handleRequestGetDiagnostics(
796 commandPayload
: GetDiagnosticsRequest
797 ): Promise
<GetDiagnosticsResponse
> {
799 this.chargingStation
.logPrefix() +
801 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
+
802 ' request received: %j',
805 const uri
= new URL(commandPayload
.location
);
806 if (uri
.protocol
.startsWith('ftp:')) {
807 let ftpClient
: Client
;
810 .readdirSync(path
.resolve(__dirname
, '../../../../'))
811 .filter((file
) => file
.endsWith('.log'))
812 .map((file
) => path
.join('./', file
));
813 const diagnosticsArchive
=
814 this.chargingStation
.stationInfo
.chargingStationId
+ '_logs.tar.gz';
815 tar
.create({ gzip
: true }, logFiles
).pipe(fs
.createWriteStream(diagnosticsArchive
));
816 ftpClient
= new Client();
817 const accessResponse
= await ftpClient
.access({
819 ...(!Utils
.isEmptyString(uri
.port
) && { port
: Utils
.convertToInt(uri
.port
) }),
820 ...(!Utils
.isEmptyString(uri
.username
) && { user
: uri
.username
}),
821 ...(!Utils
.isEmptyString(uri
.password
) && { password
: uri
.password
}),
823 let uploadResponse
: FTPResponse
;
824 if (accessResponse
.code
=== 220) {
825 // eslint-disable-next-line @typescript-eslint/no-misused-promises
826 ftpClient
.trackProgress(async (info
) => {
828 `${this.chargingStation.logPrefix()} ${
830 } bytes transferred from diagnostics archive ${info.name}`
832 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
833 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
835 status: OCPP16DiagnosticsStatus
.Uploading
,
839 uploadResponse
= await ftpClient
.uploadFrom(
840 path
.join(path
.resolve(__dirname
, '../../../../'), diagnosticsArchive
),
841 uri
.pathname
+ diagnosticsArchive
843 if (uploadResponse
.code
=== 226) {
844 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
845 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
847 status: OCPP16DiagnosticsStatus
.Uploaded
,
853 return { fileName
: diagnosticsArchive
};
856 ErrorType
.GENERIC_ERROR
,
857 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
858 uploadResponse?.code && '|' + uploadResponse?.code.toString()
860 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
864 ErrorType
.GENERIC_ERROR
,
865 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
866 uploadResponse?.code && '|' + uploadResponse?.code.toString()
868 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
871 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
872 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
874 status: OCPP16DiagnosticsStatus
.UploadFailed
,
880 return this.handleIncomingRequestError(
881 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
883 { errorResponse
: Constants
.OCPP_RESPONSE_EMPTY
}
888 `${this.chargingStation.logPrefix()} Unsupported protocol ${
890 } to transfer the diagnostic logs archive`
892 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
893 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
895 status: OCPP16DiagnosticsStatus
.UploadFailed
,
898 return Constants
.OCPP_RESPONSE_EMPTY
;
902 private handleRequestTriggerMessage(
903 commandPayload
: OCPP16TriggerMessageRequest
904 ): OCPP16TriggerMessageResponse
{
906 switch (commandPayload
.requestedMessage
) {
907 case MessageTrigger
.BootNotification
:
909 this.chargingStation
.ocppRequestService
911 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
914 this.chargingStation
.getBootNotificationRequest().chargePointModel
,
916 this.chargingStation
.getBootNotificationRequest().chargePointVendor
,
917 chargeBoxSerialNumber
:
918 this.chargingStation
.getBootNotificationRequest().chargeBoxSerialNumber
,
920 this.chargingStation
.getBootNotificationRequest().firmwareVersion
,
921 chargePointSerialNumber
:
922 this.chargingStation
.getBootNotificationRequest().chargePointSerialNumber
,
923 iccid
: this.chargingStation
.getBootNotificationRequest().iccid
,
924 imsi
: this.chargingStation
.getBootNotificationRequest().imsi
,
926 this.chargingStation
.getBootNotificationRequest().meterSerialNumber
,
927 meterType
: this.chargingStation
.getBootNotificationRequest().meterType
,
929 { skipBufferingOnError
: true, triggerMessage
: true }
932 /* This is intentional */
934 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
935 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
936 case MessageTrigger
.Heartbeat
:
938 this.chargingStation
.ocppRequestService
939 .sendMessageHandler(OCPP16RequestCommand
.HEARTBEAT
, null, { triggerMessage
: true })
941 /* This is intentional */
943 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
944 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
946 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
949 return this.handleIncomingRequestError(
950 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
952 { errorResponse
: Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
}