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 const connectorStatus
= this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
);
404 if (!connectorStatus
) {
406 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
407 commandPayload.connectorId
410 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
412 if (commandPayload
.connectorId
&& !Utils
.isEmptyArray(connectorStatus
.chargingProfiles
)) {
413 connectorStatus
.chargingProfiles
= [];
415 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
416 connectorStatus
.chargingProfiles
418 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
420 if (!commandPayload
.connectorId
) {
421 let clearedCP
= false;
422 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
424 !Utils
.isEmptyArray(this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)
427 .getConnectorStatus(connectorId
)
428 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
429 let clearCurrentCP
= false;
430 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
431 clearCurrentCP
= true;
434 !commandPayload
.chargingProfilePurpose
&&
435 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
437 clearCurrentCP
= true;
440 !chargingProfile
.stackLevel
&&
441 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
443 clearCurrentCP
= true;
446 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
447 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
449 clearCurrentCP
= true;
451 if (clearCurrentCP
) {
452 connectorStatus
.chargingProfiles
[index
] = {} as OCPP16ChargingProfile
;
454 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
455 connectorStatus
.chargingProfiles
463 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
466 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
469 private async handleRequestChangeAvailability(
470 commandPayload
: ChangeAvailabilityRequest
471 ): Promise
<ChangeAvailabilityResponse
> {
472 const connectorId
: number = commandPayload
.connectorId
;
473 if (!this.chargingStation
.getConnectorStatus(connectorId
)) {
475 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
477 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
479 const chargePointStatus
: OCPP16ChargePointStatus
=
480 commandPayload
.type === OCPP16AvailabilityType
.OPERATIVE
481 ? OCPP16ChargePointStatus
.AVAILABLE
482 : OCPP16ChargePointStatus
.UNAVAILABLE
;
483 if (connectorId
=== 0) {
484 let response
: ChangeAvailabilityResponse
= Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
485 for (const id
of this.chargingStation
.connectors
.keys()) {
486 if (this.chargingStation
.getConnectorStatus(id
)?.transactionStarted
) {
487 response
= Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
489 this.chargingStation
.getConnectorStatus(id
).availability
= commandPayload
.type;
490 if (response
=== Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
491 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
492 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
495 status: chargePointStatus
,
496 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
499 this.chargingStation
.getConnectorStatus(id
).status = chargePointStatus
;
505 (this.chargingStation
.getConnectorStatus(0).availability
===
506 OCPP16AvailabilityType
.OPERATIVE
||
507 (this.chargingStation
.getConnectorStatus(0).availability
===
508 OCPP16AvailabilityType
.INOPERATIVE
&&
509 commandPayload
.type === OCPP16AvailabilityType
.INOPERATIVE
))
511 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
512 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
513 return Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
515 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
516 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
517 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
518 { connectorId
, status: chargePointStatus
, errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
}
520 this.chargingStation
.getConnectorStatus(connectorId
).status = chargePointStatus
;
521 return Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
523 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
526 private async handleRequestRemoteStartTransaction(
527 commandPayload
: RemoteStartTransactionRequest
528 ): Promise
<DefaultResponse
> {
529 const transactionConnectorId
= commandPayload
.connectorId
;
530 const connectorStatus
= this.chargingStation
.getConnectorStatus(transactionConnectorId
);
531 if (transactionConnectorId
) {
532 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
533 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
535 connectorId
: transactionConnectorId
,
536 status: OCPP16ChargePointStatus
.PREPARING
,
537 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
540 connectorStatus
.status = OCPP16ChargePointStatus
.PREPARING
;
541 if (this.chargingStation
.isChargingStationAvailable() && connectorStatus
) {
542 // Check if authorized
543 if (this.chargingStation
.getAuthorizeRemoteTxRequests()) {
544 let authorized
= false;
546 this.chargingStation
.getLocalAuthListEnabled() &&
547 this.chargingStation
.hasAuthorizedTags() &&
548 this.chargingStation
.authorizedTags
.find((value
) => value
=== commandPayload
.idTag
)
550 connectorStatus
.localAuthorizeIdTag
= commandPayload
.idTag
;
551 connectorStatus
.idTagLocalAuthorized
= true;
553 } else if (this.chargingStation
.getMayAuthorizeAtRemoteStart()) {
554 connectorStatus
.authorizeIdTag
= commandPayload
.idTag
;
555 const authorizeResponse
: OCPP16AuthorizeResponse
=
556 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
557 OCPP16RequestCommand
.AUTHORIZE
,
559 idTag
: commandPayload
.idTag
,
561 )) as OCPP16AuthorizeResponse
;
562 if (authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
567 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
571 // Authorization successful, start transaction
573 this.setRemoteStartTransactionChargingProfile(
574 transactionConnectorId
,
575 commandPayload
.chargingProfile
578 connectorStatus
.transactionRemoteStarted
= true;
581 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
582 OCPP16RequestCommand
.START_TRANSACTION
,
584 connectorId
: transactionConnectorId
,
585 idTag
: commandPayload
.idTag
,
587 )) as OCPP16StartTransactionResponse
588 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
591 this.chargingStation
.logPrefix() +
592 ' Transaction remotely STARTED on ' +
593 this.chargingStation
.stationInfo
.chargingStationId
+
595 transactionConnectorId
.toString() +
599 return Constants
.OCPP_RESPONSE_ACCEPTED
;
601 return this.notifyRemoteStartTransactionRejected(
602 transactionConnectorId
,
606 return this.notifyRemoteStartTransactionRejected(
607 transactionConnectorId
,
611 return this.notifyRemoteStartTransactionRejected(
612 transactionConnectorId
,
616 // No authorization check required, start transaction
618 this.setRemoteStartTransactionChargingProfile(
619 transactionConnectorId
,
620 commandPayload
.chargingProfile
623 connectorStatus
.transactionRemoteStarted
= true;
626 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
627 OCPP16RequestCommand
.START_TRANSACTION
,
629 connectorId
: transactionConnectorId
,
630 idTag
: commandPayload
.idTag
,
632 )) as OCPP16StartTransactionResponse
633 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
636 this.chargingStation
.logPrefix() +
637 ' Transaction remotely STARTED on ' +
638 this.chargingStation
.stationInfo
.chargingStationId
+
640 transactionConnectorId
.toString() +
644 return Constants
.OCPP_RESPONSE_ACCEPTED
;
646 return this.notifyRemoteStartTransactionRejected(
647 transactionConnectorId
,
651 return this.notifyRemoteStartTransactionRejected(
652 transactionConnectorId
,
656 return this.notifyRemoteStartTransactionRejected(
657 transactionConnectorId
,
661 return this.notifyRemoteStartTransactionRejected(transactionConnectorId
, commandPayload
.idTag
);
664 private async notifyRemoteStartTransactionRejected(
667 ): Promise
<DefaultResponse
> {
669 this.chargingStation
.getConnectorStatus(connectorId
).status !==
670 OCPP16ChargePointStatus
.AVAILABLE
672 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
673 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
676 status: OCPP16ChargePointStatus
.AVAILABLE
,
677 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
680 this.chargingStation
.getConnectorStatus(connectorId
).status =
681 OCPP16ChargePointStatus
.AVAILABLE
;
684 this.chargingStation
.logPrefix() +
685 ' Remote starting transaction REJECTED on connector Id ' +
686 connectorId
.toString() +
690 this.chargingStation
.getConnectorStatus(connectorId
).availability
+
692 this.chargingStation
.getConnectorStatus(connectorId
).status
694 return Constants
.OCPP_RESPONSE_REJECTED
;
697 private setRemoteStartTransactionChargingProfile(
699 cp
: OCPP16ChargingProfile
701 if (cp
&& cp
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
) {
702 this.chargingStation
.setChargingProfile(connectorId
, cp
);
704 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`,
705 this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
708 } else if (cp
&& cp
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
) {
710 `${this.chargingStation.logPrefix()} Not allowed to set ${
711 cp.chargingProfilePurpose
712 } charging profile(s) at remote start transaction`
720 private async handleRequestRemoteStopTransaction(
721 commandPayload
: RemoteStopTransactionRequest
722 ): Promise
<DefaultResponse
> {
723 const transactionId
= commandPayload
.transactionId
;
724 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
727 this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
729 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
730 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
733 status: OCPP16ChargePointStatus
.FINISHING
,
734 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
737 this.chargingStation
.getConnectorStatus(connectorId
).status =
738 OCPP16ChargePointStatus
.FINISHING
;
740 this.chargingStation
.getBeginEndMeterValues() &&
741 this.chargingStation
.getOcppStrictCompliance() &&
742 !this.chargingStation
.getOutOfOrderEndMeterValues()
744 // FIXME: Implement OCPP version agnostic helpers
745 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
746 this.chargingStation
,
748 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
750 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
751 OCPP16RequestCommand
.METER_VALUES
,
755 meterValue
: transactionEndMeterValue
,
759 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
760 OCPP16RequestCommand
.STOP_TRANSACTION
,
764 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
765 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
768 return Constants
.OCPP_RESPONSE_ACCEPTED
;
772 this.chargingStation
.logPrefix() +
773 ' Trying to remote stop a non existing transaction ' +
774 transactionId
.toString()
776 return Constants
.OCPP_RESPONSE_REJECTED
;
779 private async handleRequestGetDiagnostics(
780 commandPayload
: GetDiagnosticsRequest
781 ): Promise
<GetDiagnosticsResponse
> {
783 this.chargingStation
.logPrefix() +
785 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
+
786 ' request received: %j',
789 const uri
= new URL(commandPayload
.location
);
790 if (uri
.protocol
.startsWith('ftp:')) {
791 let ftpClient
: Client
;
794 .readdirSync(path
.resolve(__dirname
, '../../../../'))
795 .filter((file
) => file
.endsWith('.log'))
796 .map((file
) => path
.join('./', file
));
797 const diagnosticsArchive
=
798 this.chargingStation
.stationInfo
.chargingStationId
+ '_logs.tar.gz';
799 tar
.create({ gzip
: true }, logFiles
).pipe(fs
.createWriteStream(diagnosticsArchive
));
800 ftpClient
= new Client();
801 const accessResponse
= await ftpClient
.access({
803 ...(!Utils
.isEmptyString(uri
.port
) && { port
: Utils
.convertToInt(uri
.port
) }),
804 ...(!Utils
.isEmptyString(uri
.username
) && { user
: uri
.username
}),
805 ...(!Utils
.isEmptyString(uri
.password
) && { password
: uri
.password
}),
807 let uploadResponse
: FTPResponse
;
808 if (accessResponse
.code
=== 220) {
809 // eslint-disable-next-line @typescript-eslint/no-misused-promises
810 ftpClient
.trackProgress(async (info
) => {
812 `${this.chargingStation.logPrefix()} ${
814 } bytes transferred from diagnostics archive ${info.name}`
816 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
817 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
819 status: OCPP16DiagnosticsStatus
.Uploading
,
823 uploadResponse
= await ftpClient
.uploadFrom(
824 path
.join(path
.resolve(__dirname
, '../../../../'), diagnosticsArchive
),
825 uri
.pathname
+ diagnosticsArchive
827 if (uploadResponse
.code
=== 226) {
828 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
829 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
831 status: OCPP16DiagnosticsStatus
.Uploaded
,
837 return { fileName
: diagnosticsArchive
};
840 ErrorType
.GENERIC_ERROR
,
841 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
842 uploadResponse?.code && '|' + uploadResponse?.code.toString()
844 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
848 ErrorType
.GENERIC_ERROR
,
849 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
850 uploadResponse?.code && '|' + uploadResponse?.code.toString()
852 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
855 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
856 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
858 status: OCPP16DiagnosticsStatus
.UploadFailed
,
864 return this.handleIncomingRequestError(
865 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
867 { errorResponse
: Constants
.OCPP_RESPONSE_EMPTY
}
872 `${this.chargingStation.logPrefix()} Unsupported protocol ${
874 } to transfer the diagnostic logs archive`
876 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
877 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
879 status: OCPP16DiagnosticsStatus
.UploadFailed
,
882 return Constants
.OCPP_RESPONSE_EMPTY
;
886 private handleRequestTriggerMessage(
887 commandPayload
: OCPP16TriggerMessageRequest
888 ): OCPP16TriggerMessageResponse
{
890 switch (commandPayload
.requestedMessage
) {
891 case MessageTrigger
.BootNotification
:
893 this.chargingStation
.ocppRequestService
895 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
898 this.chargingStation
.getBootNotificationRequest().chargePointModel
,
900 this.chargingStation
.getBootNotificationRequest().chargePointVendor
,
901 chargeBoxSerialNumber
:
902 this.chargingStation
.getBootNotificationRequest().chargeBoxSerialNumber
,
904 this.chargingStation
.getBootNotificationRequest().firmwareVersion
,
905 chargePointSerialNumber
:
906 this.chargingStation
.getBootNotificationRequest().chargePointSerialNumber
,
907 iccid
: this.chargingStation
.getBootNotificationRequest().iccid
,
908 imsi
: this.chargingStation
.getBootNotificationRequest().imsi
,
910 this.chargingStation
.getBootNotificationRequest().meterSerialNumber
,
911 meterType
: this.chargingStation
.getBootNotificationRequest().meterType
,
913 { skipBufferingOnError
: true, triggerMessage
: true }
916 /* This is intentional */
918 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
919 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
920 case MessageTrigger
.Heartbeat
:
922 this.chargingStation
.ocppRequestService
923 .sendMessageHandler(OCPP16RequestCommand
.HEARTBEAT
, null, { triggerMessage
: true })
925 /* This is intentional */
927 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
928 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
930 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
933 return this.handleIncomingRequestError(
934 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
936 { errorResponse
: Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
}