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 { JsonType
} 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 handleRequest(
131 commandName
: OCPP16IncomingRequestCommand
,
132 commandPayload
: JsonType
134 let result
: JsonType
;
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 result
158 result
= 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 result
188 await this.chargingStation
.ocppRequestService
.sendResult(messageId
, result
, 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
.stop(
196 (commandPayload
.type + 'Reset') as OCPP16StopTransactionReason
198 await Utils
.sleep(this.chargingStation
.stationInfo
.resetTime
);
199 this.chargingStation
.start();
202 `${this.chargingStation.logPrefix()} ${
204 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
205 this.chargingStation.stationInfo.resetTime
208 return Constants
.OCPP_RESPONSE_ACCEPTED
;
211 private handleRequestClearCache(): DefaultResponse
{
212 return Constants
.OCPP_RESPONSE_ACCEPTED
;
215 private async handleRequestUnlockConnector(
216 commandPayload
: UnlockConnectorRequest
217 ): Promise
<UnlockConnectorResponse
> {
218 const connectorId
= commandPayload
.connectorId
;
219 if (connectorId
=== 0) {
221 this.chargingStation
.logPrefix() + ' Trying to unlock connector ' + connectorId
.toString()
223 return Constants
.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
;
225 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
226 const transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
228 this.chargingStation
.getBeginEndMeterValues() &&
229 this.chargingStation
.getOcppStrictCompliance() &&
230 !this.chargingStation
.getOutOfOrderEndMeterValues()
232 // FIXME: Implement OCPP version agnostic helpers
233 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
234 this.chargingStation
,
236 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
238 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
239 OCPP16MeterValuesRequest
,
240 OCPP16MeterValuesResponse
241 >(OCPP16RequestCommand
.METER_VALUES
, {
244 meterValue
: transactionEndMeterValue
,
247 const stopResponse
= await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
248 OCPP16StopTransactionRequest
,
249 OCPP16StopTransactionResponse
250 >(OCPP16RequestCommand
.STOP_TRANSACTION
, {
252 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
253 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
254 reason
: OCPP16StopTransactionReason
.UNLOCK_COMMAND
,
256 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
257 return Constants
.OCPP_RESPONSE_UNLOCKED
;
259 return Constants
.OCPP_RESPONSE_UNLOCK_FAILED
;
261 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
262 OCPP16StatusNotificationRequest
,
263 OCPP16StatusNotificationResponse
264 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
266 status: OCPP16ChargePointStatus
.AVAILABLE
,
267 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
269 this.chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
270 return Constants
.OCPP_RESPONSE_UNLOCKED
;
273 private handleRequestGetConfiguration(
274 commandPayload
: GetConfigurationRequest
275 ): GetConfigurationResponse
{
276 const configurationKey
: OCPPConfigurationKey
[] = [];
277 const unknownKey
: string[] = [];
278 if (Utils
.isEmptyArray(commandPayload
.key
)) {
279 for (const configuration
of this.chargingStation
.ocppConfiguration
.configurationKey
) {
280 if (Utils
.isUndefined(configuration
.visible
)) {
281 configuration
.visible
= true;
283 if (!configuration
.visible
) {
286 configurationKey
.push({
287 key
: configuration
.key
,
288 readonly: configuration
.readonly,
289 value
: configuration
.value
,
293 for (const key
of commandPayload
.key
) {
294 const keyFound
= this.chargingStation
.getConfigurationKey(key
);
296 if (Utils
.isUndefined(keyFound
.visible
)) {
297 keyFound
.visible
= true;
299 if (!keyFound
.visible
) {
302 configurationKey
.push({
304 readonly: keyFound
.readonly,
305 value
: keyFound
.value
,
308 unknownKey
.push(key
);
318 private handleRequestChangeConfiguration(
319 commandPayload
: ChangeConfigurationRequest
320 ): ChangeConfigurationResponse
{
321 // JSON request fields type sanity check
322 if (!Utils
.isString(commandPayload
.key
)) {
324 `${this.chargingStation.logPrefix()} ${
325 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
326 } request key field is not a string:`,
330 if (!Utils
.isString(commandPayload
.value
)) {
332 `${this.chargingStation.logPrefix()} ${
333 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
334 } request value field is not a string:`,
338 const keyToChange
= this.chargingStation
.getConfigurationKey(commandPayload
.key
, true);
340 return Constants
.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
;
341 } else if (keyToChange
&& keyToChange
.readonly) {
342 return Constants
.OCPP_CONFIGURATION_RESPONSE_REJECTED
;
343 } else if (keyToChange
&& !keyToChange
.readonly) {
344 let valueChanged
= false;
345 if (keyToChange
.value
!== commandPayload
.value
) {
346 this.chargingStation
.setConfigurationKeyValue(
348 commandPayload
.value
,
353 let triggerHeartbeatRestart
= false;
354 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartBeatInterval
&& valueChanged
) {
355 this.chargingStation
.setConfigurationKeyValue(
356 OCPP16StandardParametersKey
.HeartbeatInterval
,
359 triggerHeartbeatRestart
= true;
361 if (keyToChange
.key
=== OCPP16StandardParametersKey
.HeartbeatInterval
&& valueChanged
) {
362 this.chargingStation
.setConfigurationKeyValue(
363 OCPP16StandardParametersKey
.HeartBeatInterval
,
366 triggerHeartbeatRestart
= true;
368 if (triggerHeartbeatRestart
) {
369 this.chargingStation
.restartHeartbeat();
371 if (keyToChange
.key
=== OCPP16StandardParametersKey
.WebSocketPingInterval
&& valueChanged
) {
372 this.chargingStation
.restartWebSocketPing();
374 if (keyToChange
.reboot
) {
375 return Constants
.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
;
377 return Constants
.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
;
381 private handleRequestSetChargingProfile(
382 commandPayload
: SetChargingProfileRequest
383 ): SetChargingProfileResponse
{
384 if (!this.chargingStation
.hasFeatureProfile(OCPP16SupportedFeatureProfiles
.SmartCharging
)) {
386 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) without '${
387 OCPP16SupportedFeatureProfiles.SmartCharging
388 }' feature enabled in ${
389 OCPP16StandardParametersKey.SupportedFeatureProfiles
392 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
;
394 if (!this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)) {
396 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
397 commandPayload.connectorId
400 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
403 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
404 ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
405 commandPayload
.connectorId
!== 0
407 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
410 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
411 ChargingProfilePurposeType
.TX_PROFILE
&&
412 (commandPayload
.connectorId
=== 0 ||
413 !this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)?.transactionStarted
)
415 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
417 this.chargingStation
.setChargingProfile(
418 commandPayload
.connectorId
,
419 commandPayload
.csChargingProfiles
422 `${this.chargingStation.logPrefix()} Charging profile(s) set on connector id ${
423 commandPayload.connectorId
424 }, dump their stack: %j`,
425 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
427 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
430 private handleRequestClearChargingProfile(
431 commandPayload
: ClearChargingProfileRequest
432 ): ClearChargingProfileResponse
{
433 if (!this.chargingStation
.hasFeatureProfile(OCPP16SupportedFeatureProfiles
.SmartCharging
)) {
435 `${this.chargingStation.logPrefix()} Trying to clear charging profile(s) without '${
436 OCPP16SupportedFeatureProfiles.SmartCharging
437 }' feature enabled in ${
438 OCPP16StandardParametersKey.SupportedFeatureProfiles
441 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
443 const connectorStatus
= this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
);
444 if (!connectorStatus
) {
446 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
447 commandPayload.connectorId
450 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
452 if (commandPayload
.connectorId
&& !Utils
.isEmptyArray(connectorStatus
.chargingProfiles
)) {
453 connectorStatus
.chargingProfiles
= [];
455 `${this.chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
456 commandPayload.connectorId
457 }, dump their stack: %j`,
458 connectorStatus
.chargingProfiles
460 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
462 if (!commandPayload
.connectorId
) {
463 let clearedCP
= false;
464 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
466 !Utils
.isEmptyArray(this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)
469 .getConnectorStatus(connectorId
)
470 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
471 let clearCurrentCP
= false;
472 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
473 clearCurrentCP
= true;
476 !commandPayload
.chargingProfilePurpose
&&
477 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
479 clearCurrentCP
= true;
482 !chargingProfile
.stackLevel
&&
483 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
485 clearCurrentCP
= true;
488 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
489 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
491 clearCurrentCP
= true;
493 if (clearCurrentCP
) {
494 connectorStatus
.chargingProfiles
[index
] = {} as OCPP16ChargingProfile
;
496 `${this.chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
497 commandPayload.connectorId
498 }, dump their stack: %j`,
499 connectorStatus
.chargingProfiles
507 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
510 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
513 private async handleRequestChangeAvailability(
514 commandPayload
: ChangeAvailabilityRequest
515 ): Promise
<ChangeAvailabilityResponse
> {
516 const connectorId
: number = commandPayload
.connectorId
;
517 if (!this.chargingStation
.getConnectorStatus(connectorId
)) {
519 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
521 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
523 const chargePointStatus
: OCPP16ChargePointStatus
=
524 commandPayload
.type === OCPP16AvailabilityType
.OPERATIVE
525 ? OCPP16ChargePointStatus
.AVAILABLE
526 : OCPP16ChargePointStatus
.UNAVAILABLE
;
527 if (connectorId
=== 0) {
528 let response
: ChangeAvailabilityResponse
= Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
529 for (const id
of this.chargingStation
.connectors
.keys()) {
530 if (this.chargingStation
.getConnectorStatus(id
)?.transactionStarted
) {
531 response
= Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
533 this.chargingStation
.getConnectorStatus(id
).availability
= commandPayload
.type;
534 if (response
=== Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
535 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
536 OCPP16StatusNotificationRequest
,
537 OCPP16StatusNotificationResponse
538 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
540 status: chargePointStatus
,
541 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
543 this.chargingStation
.getConnectorStatus(id
).status = chargePointStatus
;
549 (this.chargingStation
.getConnectorStatus(0).availability
===
550 OCPP16AvailabilityType
.OPERATIVE
||
551 (this.chargingStation
.getConnectorStatus(0).availability
===
552 OCPP16AvailabilityType
.INOPERATIVE
&&
553 commandPayload
.type === OCPP16AvailabilityType
.INOPERATIVE
))
555 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
556 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
557 return Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
559 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
560 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
561 OCPP16StatusNotificationRequest
,
562 OCPP16StatusNotificationResponse
563 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
565 status: chargePointStatus
,
566 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
568 this.chargingStation
.getConnectorStatus(connectorId
).status = chargePointStatus
;
569 return Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
571 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
574 private async handleRequestRemoteStartTransaction(
575 commandPayload
: RemoteStartTransactionRequest
576 ): Promise
<DefaultResponse
> {
577 const transactionConnectorId
= commandPayload
.connectorId
;
578 const connectorStatus
= this.chargingStation
.getConnectorStatus(transactionConnectorId
);
579 if (transactionConnectorId
) {
580 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
581 OCPP16StatusNotificationRequest
,
582 OCPP16StatusNotificationResponse
583 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
584 connectorId
: transactionConnectorId
,
585 status: OCPP16ChargePointStatus
.PREPARING
,
586 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
588 connectorStatus
.status = OCPP16ChargePointStatus
.PREPARING
;
589 if (this.chargingStation
.isChargingStationAvailable() && connectorStatus
) {
590 // Check if authorized
591 if (this.chargingStation
.getAuthorizeRemoteTxRequests()) {
592 let authorized
= false;
594 this.chargingStation
.getLocalAuthListEnabled() &&
595 this.chargingStation
.hasAuthorizedTags() &&
596 this.chargingStation
.authorizedTags
.find((value
) => value
=== commandPayload
.idTag
)
598 connectorStatus
.localAuthorizeIdTag
= commandPayload
.idTag
;
599 connectorStatus
.idTagLocalAuthorized
= true;
601 } else if (this.chargingStation
.getMayAuthorizeAtRemoteStart()) {
602 connectorStatus
.authorizeIdTag
= commandPayload
.idTag
;
603 const authorizeResponse
: OCPP16AuthorizeResponse
=
604 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
605 OCPP16AuthorizeRequest
,
606 OCPP16AuthorizeResponse
607 >(OCPP16RequestCommand
.AUTHORIZE
, {
608 idTag
: commandPayload
.idTag
,
610 if (authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
615 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
619 // Authorization successful, start transaction
621 this.setRemoteStartTransactionChargingProfile(
622 transactionConnectorId
,
623 commandPayload
.chargingProfile
626 connectorStatus
.transactionRemoteStarted
= true;
629 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
630 OCPP16StartTransactionRequest
,
631 OCPP16StartTransactionResponse
632 >(OCPP16RequestCommand
.START_TRANSACTION
, {
633 connectorId
: transactionConnectorId
,
634 idTag
: commandPayload
.idTag
,
636 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
639 this.chargingStation
.logPrefix() +
640 ' Transaction remotely STARTED on ' +
641 this.chargingStation
.stationInfo
.chargingStationId
+
643 transactionConnectorId
.toString() +
647 return Constants
.OCPP_RESPONSE_ACCEPTED
;
649 return this.notifyRemoteStartTransactionRejected(
650 transactionConnectorId
,
654 return this.notifyRemoteStartTransactionRejected(
655 transactionConnectorId
,
659 return this.notifyRemoteStartTransactionRejected(
660 transactionConnectorId
,
664 // No authorization check required, start transaction
666 this.setRemoteStartTransactionChargingProfile(
667 transactionConnectorId
,
668 commandPayload
.chargingProfile
671 connectorStatus
.transactionRemoteStarted
= true;
674 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
675 OCPP16StartTransactionRequest
,
676 OCPP16StartTransactionResponse
677 >(OCPP16RequestCommand
.START_TRANSACTION
, {
678 connectorId
: transactionConnectorId
,
679 idTag
: commandPayload
.idTag
,
681 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
684 this.chargingStation
.logPrefix() +
685 ' Transaction remotely STARTED on ' +
686 this.chargingStation
.stationInfo
.chargingStationId
+
688 transactionConnectorId
.toString() +
692 return Constants
.OCPP_RESPONSE_ACCEPTED
;
694 return this.notifyRemoteStartTransactionRejected(
695 transactionConnectorId
,
699 return this.notifyRemoteStartTransactionRejected(
700 transactionConnectorId
,
704 return this.notifyRemoteStartTransactionRejected(
705 transactionConnectorId
,
709 return this.notifyRemoteStartTransactionRejected(transactionConnectorId
, commandPayload
.idTag
);
712 private async notifyRemoteStartTransactionRejected(
715 ): Promise
<DefaultResponse
> {
717 this.chargingStation
.getConnectorStatus(connectorId
).status !==
718 OCPP16ChargePointStatus
.AVAILABLE
720 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
721 OCPP16StatusNotificationRequest
,
722 OCPP16StatusNotificationResponse
723 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
725 status: OCPP16ChargePointStatus
.AVAILABLE
,
726 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
728 this.chargingStation
.getConnectorStatus(connectorId
).status =
729 OCPP16ChargePointStatus
.AVAILABLE
;
732 this.chargingStation
.logPrefix() +
733 ' Remote starting transaction REJECTED on connector Id ' +
734 connectorId
.toString() +
738 this.chargingStation
.getConnectorStatus(connectorId
).availability
+
740 this.chargingStation
.getConnectorStatus(connectorId
).status
742 return Constants
.OCPP_RESPONSE_REJECTED
;
745 private setRemoteStartTransactionChargingProfile(
747 cp
: OCPP16ChargingProfile
749 if (cp
&& cp
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
) {
750 this.chargingStation
.setChargingProfile(connectorId
, cp
);
752 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
753 this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
756 } else if (cp
&& cp
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
) {
758 `${this.chargingStation.logPrefix()} Not allowed to set ${
759 cp.chargingProfilePurpose
760 } charging profile(s) at remote start transaction`
768 private async handleRequestRemoteStopTransaction(
769 commandPayload
: RemoteStopTransactionRequest
770 ): Promise
<DefaultResponse
> {
771 const transactionId
= commandPayload
.transactionId
;
772 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
775 this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
777 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
778 OCPP16StatusNotificationRequest
,
779 OCPP16StatusNotificationResponse
780 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
782 status: OCPP16ChargePointStatus
.FINISHING
,
783 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
785 this.chargingStation
.getConnectorStatus(connectorId
).status =
786 OCPP16ChargePointStatus
.FINISHING
;
788 this.chargingStation
.getBeginEndMeterValues() &&
789 this.chargingStation
.getOcppStrictCompliance() &&
790 !this.chargingStation
.getOutOfOrderEndMeterValues()
792 // FIXME: Implement OCPP version agnostic helpers
793 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
794 this.chargingStation
,
796 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
798 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
799 OCPP16MeterValuesRequest
,
800 OCPP16MeterValuesResponse
801 >(OCPP16RequestCommand
.METER_VALUES
, {
804 meterValue
: transactionEndMeterValue
,
807 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
808 OCPP16StopTransactionRequest
,
809 OCPP16StopTransactionResponse
810 >(OCPP16RequestCommand
.STOP_TRANSACTION
, {
813 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
814 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
816 return Constants
.OCPP_RESPONSE_ACCEPTED
;
820 this.chargingStation
.logPrefix() +
821 ' Trying to remote stop a non existing transaction ' +
822 transactionId
.toString()
824 return Constants
.OCPP_RESPONSE_REJECTED
;
827 private async handleRequestGetDiagnostics(
828 commandPayload
: GetDiagnosticsRequest
829 ): Promise
<GetDiagnosticsResponse
> {
831 !this.chargingStation
.hasFeatureProfile(OCPP16SupportedFeatureProfiles
.FirmwareManagement
)
834 `${this.chargingStation.logPrefix()} Trying to get diagnostics without '${
835 OCPP16SupportedFeatureProfiles.FirmwareManagement
836 }' feature enabled in ${
837 OCPP16StandardParametersKey.SupportedFeatureProfiles
840 return Constants
.OCPP_RESPONSE_EMPTY
;
843 this.chargingStation
.logPrefix() +
845 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
+
846 ' request received: %j',
849 const uri
= new URL(commandPayload
.location
);
850 if (uri
.protocol
.startsWith('ftp:')) {
851 let ftpClient
: Client
;
854 .readdirSync(path
.resolve(__dirname
, '../../../../'))
855 .filter((file
) => file
.endsWith('.log'))
856 .map((file
) => path
.join('./', file
));
857 const diagnosticsArchive
=
858 this.chargingStation
.stationInfo
.chargingStationId
+ '_logs.tar.gz';
859 tar
.create({ gzip
: true }, logFiles
).pipe(fs
.createWriteStream(diagnosticsArchive
));
860 ftpClient
= new Client();
861 const accessResponse
= await ftpClient
.access({
863 ...(!Utils
.isEmptyString(uri
.port
) && { port
: Utils
.convertToInt(uri
.port
) }),
864 ...(!Utils
.isEmptyString(uri
.username
) && { user
: uri
.username
}),
865 ...(!Utils
.isEmptyString(uri
.password
) && { password
: uri
.password
}),
867 let uploadResponse
: FTPResponse
;
868 if (accessResponse
.code
=== 220) {
869 // eslint-disable-next-line @typescript-eslint/no-misused-promises
870 ftpClient
.trackProgress(async (info
) => {
872 `${this.chargingStation.logPrefix()} ${
874 } bytes transferred from diagnostics archive ${info.name}`
876 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
877 DiagnosticsStatusNotificationRequest
,
878 DiagnosticsStatusNotificationResponse
879 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
880 status: OCPP16DiagnosticsStatus
.Uploading
,
883 uploadResponse
= await ftpClient
.uploadFrom(
884 path
.join(path
.resolve(__dirname
, '../../../../'), diagnosticsArchive
),
885 uri
.pathname
+ diagnosticsArchive
887 if (uploadResponse
.code
=== 226) {
888 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
889 DiagnosticsStatusNotificationRequest
,
890 DiagnosticsStatusNotificationResponse
891 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
892 status: OCPP16DiagnosticsStatus
.Uploaded
,
897 return { fileName
: diagnosticsArchive
};
900 ErrorType
.GENERIC_ERROR
,
901 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
902 uploadResponse?.code && '|' + uploadResponse?.code.toString()
904 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
908 ErrorType
.GENERIC_ERROR
,
909 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
910 uploadResponse?.code && '|' + uploadResponse?.code.toString()
912 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
915 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
916 DiagnosticsStatusNotificationRequest
,
917 DiagnosticsStatusNotificationResponse
918 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
919 status: OCPP16DiagnosticsStatus
.UploadFailed
,
924 return this.handleIncomingRequestError(
925 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
927 { errorResponse
: Constants
.OCPP_RESPONSE_EMPTY
}
932 `${this.chargingStation.logPrefix()} Unsupported protocol ${
934 } to transfer the diagnostic logs archive`
936 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
937 DiagnosticsStatusNotificationRequest
,
938 DiagnosticsStatusNotificationResponse
939 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
940 status: OCPP16DiagnosticsStatus
.UploadFailed
,
942 return Constants
.OCPP_RESPONSE_EMPTY
;
946 private handleRequestTriggerMessage(
947 commandPayload
: OCPP16TriggerMessageRequest
948 ): OCPP16TriggerMessageResponse
{
949 if (!this.chargingStation
.hasFeatureProfile(OCPP16SupportedFeatureProfiles
.RemoteTrigger
)) {
951 `${this.chargingStation.logPrefix()} Trying to remote trigger message without '${
952 OCPP16SupportedFeatureProfiles.RemoteTrigger
953 }' feature enabled in ${
954 OCPP16StandardParametersKey.SupportedFeatureProfiles
957 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
960 switch (commandPayload
.requestedMessage
) {
961 case MessageTrigger
.BootNotification
:
963 this.chargingStation
.ocppRequestService
964 .sendMessageHandler
<OCPP16BootNotificationRequest
, OCPP16BootNotificationResponse
>(
965 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
968 this.chargingStation
.getBootNotificationRequest().chargePointModel
,
970 this.chargingStation
.getBootNotificationRequest().chargePointVendor
,
971 chargeBoxSerialNumber
:
972 this.chargingStation
.getBootNotificationRequest().chargeBoxSerialNumber
,
974 this.chargingStation
.getBootNotificationRequest().firmwareVersion
,
975 chargePointSerialNumber
:
976 this.chargingStation
.getBootNotificationRequest().chargePointSerialNumber
,
977 iccid
: this.chargingStation
.getBootNotificationRequest().iccid
,
978 imsi
: this.chargingStation
.getBootNotificationRequest().imsi
,
980 this.chargingStation
.getBootNotificationRequest().meterSerialNumber
,
981 meterType
: this.chargingStation
.getBootNotificationRequest().meterType
,
983 { skipBufferingOnError
: true, triggerMessage
: true }
986 this.chargingStation
.bootNotificationResponse
= value
;
989 /* This is intentional */
991 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
992 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
993 case MessageTrigger
.Heartbeat
:
995 this.chargingStation
.ocppRequestService
996 .sendMessageHandler
<OCPP16HeartbeatRequest
, OCPP16HeartbeatResponse
>(
997 OCPP16RequestCommand
.HEARTBEAT
,
1000 triggerMessage
: true,
1004 /* This is intentional */
1006 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1007 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1009 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1012 return this.handleIncomingRequestError(
1013 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1015 { errorResponse
: Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
}