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 incomingRequestHandler(
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
.requestHandler
<
239 OCPP16MeterValuesRequest
,
240 OCPP16MeterValuesResponse
241 >(OCPP16RequestCommand
.METER_VALUES
, {
244 meterValue
: transactionEndMeterValue
,
247 const stopResponse
= await this.chargingStation
.ocppRequestService
.requestHandler
<
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
.requestHandler
<
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
{
385 !OCPP16ServiceUtils
.checkFeatureProfile(
386 this.chargingStation
,
387 OCPP16SupportedFeatureProfiles
.SmartCharging
,
388 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
391 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
;
393 if (!this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)) {
395 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
396 commandPayload.connectorId
399 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
402 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
403 ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
&&
404 commandPayload
.connectorId
!== 0
406 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
409 commandPayload
.csChargingProfiles
.chargingProfilePurpose
===
410 ChargingProfilePurposeType
.TX_PROFILE
&&
411 (commandPayload
.connectorId
=== 0 ||
412 !this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
)?.transactionStarted
)
414 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
;
416 this.chargingStation
.setChargingProfile(
417 commandPayload
.connectorId
,
418 commandPayload
.csChargingProfiles
421 `${this.chargingStation.logPrefix()} Charging profile(s) set on connector id ${
422 commandPayload.connectorId
423 }, dump their stack: %j`,
424 this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
).chargingProfiles
426 return Constants
.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
429 private handleRequestClearChargingProfile(
430 commandPayload
: ClearChargingProfileRequest
431 ): ClearChargingProfileResponse
{
433 !OCPP16ServiceUtils
.checkFeatureProfile(
434 this.chargingStation
,
435 OCPP16SupportedFeatureProfiles
.SmartCharging
,
436 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
439 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
441 const connectorStatus
= this.chargingStation
.getConnectorStatus(commandPayload
.connectorId
);
442 if (!connectorStatus
) {
444 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
445 commandPayload.connectorId
448 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
450 if (commandPayload
.connectorId
&& !Utils
.isEmptyArray(connectorStatus
.chargingProfiles
)) {
451 connectorStatus
.chargingProfiles
= [];
453 `${this.chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
454 commandPayload.connectorId
455 }, dump their stack: %j`,
456 connectorStatus
.chargingProfiles
458 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
460 if (!commandPayload
.connectorId
) {
461 let clearedCP
= false;
462 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
464 !Utils
.isEmptyArray(this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)
467 .getConnectorStatus(connectorId
)
468 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
469 let clearCurrentCP
= false;
470 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
471 clearCurrentCP
= true;
474 !commandPayload
.chargingProfilePurpose
&&
475 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
477 clearCurrentCP
= true;
480 !chargingProfile
.stackLevel
&&
481 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
483 clearCurrentCP
= true;
486 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
487 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
489 clearCurrentCP
= true;
491 if (clearCurrentCP
) {
492 connectorStatus
.chargingProfiles
[index
] = {} as OCPP16ChargingProfile
;
494 `${this.chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
495 commandPayload.connectorId
496 }, dump their stack: %j`,
497 connectorStatus
.chargingProfiles
505 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
;
508 return Constants
.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
;
511 private async handleRequestChangeAvailability(
512 commandPayload
: ChangeAvailabilityRequest
513 ): Promise
<ChangeAvailabilityResponse
> {
514 const connectorId
: number = commandPayload
.connectorId
;
515 if (!this.chargingStation
.getConnectorStatus(connectorId
)) {
517 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
519 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
521 const chargePointStatus
: OCPP16ChargePointStatus
=
522 commandPayload
.type === OCPP16AvailabilityType
.OPERATIVE
523 ? OCPP16ChargePointStatus
.AVAILABLE
524 : OCPP16ChargePointStatus
.UNAVAILABLE
;
525 if (connectorId
=== 0) {
526 let response
: ChangeAvailabilityResponse
= Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
527 for (const id
of this.chargingStation
.connectors
.keys()) {
528 if (this.chargingStation
.getConnectorStatus(id
)?.transactionStarted
) {
529 response
= Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
531 this.chargingStation
.getConnectorStatus(id
).availability
= commandPayload
.type;
532 if (response
=== Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
533 await this.chargingStation
.ocppRequestService
.requestHandler
<
534 OCPP16StatusNotificationRequest
,
535 OCPP16StatusNotificationResponse
536 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
538 status: chargePointStatus
,
539 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
541 this.chargingStation
.getConnectorStatus(id
).status = chargePointStatus
;
547 (this.chargingStation
.getConnectorStatus(0).availability
===
548 OCPP16AvailabilityType
.OPERATIVE
||
549 (this.chargingStation
.getConnectorStatus(0).availability
===
550 OCPP16AvailabilityType
.INOPERATIVE
&&
551 commandPayload
.type === OCPP16AvailabilityType
.INOPERATIVE
))
553 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
554 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
555 return Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
557 this.chargingStation
.getConnectorStatus(connectorId
).availability
= commandPayload
.type;
558 await this.chargingStation
.ocppRequestService
.requestHandler
<
559 OCPP16StatusNotificationRequest
,
560 OCPP16StatusNotificationResponse
561 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
563 status: chargePointStatus
,
564 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
566 this.chargingStation
.getConnectorStatus(connectorId
).status = chargePointStatus
;
567 return Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
569 return Constants
.OCPP_AVAILABILITY_RESPONSE_REJECTED
;
572 private async handleRequestRemoteStartTransaction(
573 commandPayload
: RemoteStartTransactionRequest
574 ): Promise
<DefaultResponse
> {
575 const transactionConnectorId
= commandPayload
.connectorId
;
576 const connectorStatus
= this.chargingStation
.getConnectorStatus(transactionConnectorId
);
577 if (transactionConnectorId
) {
578 await this.chargingStation
.ocppRequestService
.requestHandler
<
579 OCPP16StatusNotificationRequest
,
580 OCPP16StatusNotificationResponse
581 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
582 connectorId
: transactionConnectorId
,
583 status: OCPP16ChargePointStatus
.PREPARING
,
584 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
586 connectorStatus
.status = OCPP16ChargePointStatus
.PREPARING
;
587 if (this.chargingStation
.isChargingStationAvailable() && connectorStatus
) {
588 // Check if authorized
589 if (this.chargingStation
.getAuthorizeRemoteTxRequests()) {
590 let authorized
= false;
592 this.chargingStation
.getLocalAuthListEnabled() &&
593 this.chargingStation
.hasAuthorizedTags() &&
594 this.chargingStation
.authorizedTags
.find((value
) => value
=== commandPayload
.idTag
)
596 connectorStatus
.localAuthorizeIdTag
= commandPayload
.idTag
;
597 connectorStatus
.idTagLocalAuthorized
= true;
599 } else if (this.chargingStation
.getMayAuthorizeAtRemoteStart()) {
600 connectorStatus
.authorizeIdTag
= commandPayload
.idTag
;
601 const authorizeResponse
: OCPP16AuthorizeResponse
=
602 await this.chargingStation
.ocppRequestService
.requestHandler
<
603 OCPP16AuthorizeRequest
,
604 OCPP16AuthorizeResponse
605 >(OCPP16RequestCommand
.AUTHORIZE
, {
606 idTag
: commandPayload
.idTag
,
608 if (authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
613 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
617 // Authorization successful, start transaction
619 this.setRemoteStartTransactionChargingProfile(
620 transactionConnectorId
,
621 commandPayload
.chargingProfile
624 connectorStatus
.transactionRemoteStarted
= true;
627 await this.chargingStation
.ocppRequestService
.requestHandler
<
628 OCPP16StartTransactionRequest
,
629 OCPP16StartTransactionResponse
630 >(OCPP16RequestCommand
.START_TRANSACTION
, {
631 connectorId
: transactionConnectorId
,
632 idTag
: commandPayload
.idTag
,
634 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
637 this.chargingStation
.logPrefix() +
638 ' Transaction remotely STARTED on ' +
639 this.chargingStation
.stationInfo
.chargingStationId
+
641 transactionConnectorId
.toString() +
645 return Constants
.OCPP_RESPONSE_ACCEPTED
;
647 return this.notifyRemoteStartTransactionRejected(
648 transactionConnectorId
,
652 return this.notifyRemoteStartTransactionRejected(
653 transactionConnectorId
,
657 return this.notifyRemoteStartTransactionRejected(
658 transactionConnectorId
,
662 // No authorization check required, start transaction
664 this.setRemoteStartTransactionChargingProfile(
665 transactionConnectorId
,
666 commandPayload
.chargingProfile
669 connectorStatus
.transactionRemoteStarted
= true;
672 await this.chargingStation
.ocppRequestService
.requestHandler
<
673 OCPP16StartTransactionRequest
,
674 OCPP16StartTransactionResponse
675 >(OCPP16RequestCommand
.START_TRANSACTION
, {
676 connectorId
: transactionConnectorId
,
677 idTag
: commandPayload
.idTag
,
679 ).idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
682 this.chargingStation
.logPrefix() +
683 ' Transaction remotely STARTED on ' +
684 this.chargingStation
.stationInfo
.chargingStationId
+
686 transactionConnectorId
.toString() +
690 return Constants
.OCPP_RESPONSE_ACCEPTED
;
692 return this.notifyRemoteStartTransactionRejected(
693 transactionConnectorId
,
697 return this.notifyRemoteStartTransactionRejected(
698 transactionConnectorId
,
702 return this.notifyRemoteStartTransactionRejected(
703 transactionConnectorId
,
707 return this.notifyRemoteStartTransactionRejected(transactionConnectorId
, commandPayload
.idTag
);
710 private async notifyRemoteStartTransactionRejected(
713 ): Promise
<DefaultResponse
> {
715 this.chargingStation
.getConnectorStatus(connectorId
).status !==
716 OCPP16ChargePointStatus
.AVAILABLE
718 await this.chargingStation
.ocppRequestService
.requestHandler
<
719 OCPP16StatusNotificationRequest
,
720 OCPP16StatusNotificationResponse
721 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
723 status: OCPP16ChargePointStatus
.AVAILABLE
,
724 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
726 this.chargingStation
.getConnectorStatus(connectorId
).status =
727 OCPP16ChargePointStatus
.AVAILABLE
;
730 this.chargingStation
.logPrefix() +
731 ' Remote starting transaction REJECTED on connector Id ' +
732 connectorId
.toString() +
736 this.chargingStation
.getConnectorStatus(connectorId
).availability
+
738 this.chargingStation
.getConnectorStatus(connectorId
).status
740 return Constants
.OCPP_RESPONSE_REJECTED
;
743 private setRemoteStartTransactionChargingProfile(
745 cp
: OCPP16ChargingProfile
747 if (cp
&& cp
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
) {
748 this.chargingStation
.setChargingProfile(connectorId
, cp
);
750 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
751 this.chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
754 } else if (cp
&& cp
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
) {
756 `${this.chargingStation.logPrefix()} Not allowed to set ${
757 cp.chargingProfilePurpose
758 } charging profile(s) at remote start transaction`
766 private async handleRequestRemoteStopTransaction(
767 commandPayload
: RemoteStopTransactionRequest
768 ): Promise
<DefaultResponse
> {
769 const transactionId
= commandPayload
.transactionId
;
770 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
773 this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== transactionId
775 await this.chargingStation
.ocppRequestService
.requestHandler
<
776 OCPP16StatusNotificationRequest
,
777 OCPP16StatusNotificationResponse
778 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
780 status: OCPP16ChargePointStatus
.FINISHING
,
781 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
783 this.chargingStation
.getConnectorStatus(connectorId
).status =
784 OCPP16ChargePointStatus
.FINISHING
;
786 this.chargingStation
.getBeginEndMeterValues() &&
787 this.chargingStation
.getOcppStrictCompliance() &&
788 !this.chargingStation
.getOutOfOrderEndMeterValues()
790 // FIXME: Implement OCPP version agnostic helpers
791 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
792 this.chargingStation
,
794 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
796 await this.chargingStation
.ocppRequestService
.requestHandler
<
797 OCPP16MeterValuesRequest
,
798 OCPP16MeterValuesResponse
799 >(OCPP16RequestCommand
.METER_VALUES
, {
802 meterValue
: transactionEndMeterValue
,
805 await this.chargingStation
.ocppRequestService
.requestHandler
<
806 OCPP16StopTransactionRequest
,
807 OCPP16StopTransactionResponse
808 >(OCPP16RequestCommand
.STOP_TRANSACTION
, {
811 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
812 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
814 return Constants
.OCPP_RESPONSE_ACCEPTED
;
818 this.chargingStation
.logPrefix() +
819 ' Trying to remote stop a non existing transaction ' +
820 transactionId
.toString()
822 return Constants
.OCPP_RESPONSE_REJECTED
;
825 private async handleRequestGetDiagnostics(
826 commandPayload
: GetDiagnosticsRequest
827 ): Promise
<GetDiagnosticsResponse
> {
829 !OCPP16ServiceUtils
.checkFeatureProfile(
830 this.chargingStation
,
831 OCPP16SupportedFeatureProfiles
.FirmwareManagement
,
832 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
835 return Constants
.OCPP_RESPONSE_EMPTY
;
838 this.chargingStation
.logPrefix() +
840 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
+
841 ' request received: %j',
844 const uri
= new URL(commandPayload
.location
);
845 if (uri
.protocol
.startsWith('ftp:')) {
846 let ftpClient
: Client
;
849 .readdirSync(path
.resolve(__dirname
, '../../../../'))
850 .filter((file
) => file
.endsWith('.log'))
851 .map((file
) => path
.join('./', file
));
852 const diagnosticsArchive
=
853 this.chargingStation
.stationInfo
.chargingStationId
+ '_logs.tar.gz';
854 tar
.create({ gzip
: true }, logFiles
).pipe(fs
.createWriteStream(diagnosticsArchive
));
855 ftpClient
= new Client();
856 const accessResponse
= await ftpClient
.access({
858 ...(!Utils
.isEmptyString(uri
.port
) && { port
: Utils
.convertToInt(uri
.port
) }),
859 ...(!Utils
.isEmptyString(uri
.username
) && { user
: uri
.username
}),
860 ...(!Utils
.isEmptyString(uri
.password
) && { password
: uri
.password
}),
862 let uploadResponse
: FTPResponse
;
863 if (accessResponse
.code
=== 220) {
864 // eslint-disable-next-line @typescript-eslint/no-misused-promises
865 ftpClient
.trackProgress(async (info
) => {
867 `${this.chargingStation.logPrefix()} ${
869 } bytes transferred from diagnostics archive ${info.name}`
871 await this.chargingStation
.ocppRequestService
.requestHandler
<
872 DiagnosticsStatusNotificationRequest
,
873 DiagnosticsStatusNotificationResponse
874 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
875 status: OCPP16DiagnosticsStatus
.Uploading
,
878 uploadResponse
= await ftpClient
.uploadFrom(
879 path
.join(path
.resolve(__dirname
, '../../../../'), diagnosticsArchive
),
880 uri
.pathname
+ diagnosticsArchive
882 if (uploadResponse
.code
=== 226) {
883 await this.chargingStation
.ocppRequestService
.requestHandler
<
884 DiagnosticsStatusNotificationRequest
,
885 DiagnosticsStatusNotificationResponse
886 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
887 status: OCPP16DiagnosticsStatus
.Uploaded
,
892 return { fileName
: diagnosticsArchive
};
895 ErrorType
.GENERIC_ERROR
,
896 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
897 uploadResponse?.code && '|' + uploadResponse?.code.toString()
899 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
903 ErrorType
.GENERIC_ERROR
,
904 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
905 uploadResponse?.code && '|' + uploadResponse?.code.toString()
907 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
910 await this.chargingStation
.ocppRequestService
.requestHandler
<
911 DiagnosticsStatusNotificationRequest
,
912 DiagnosticsStatusNotificationResponse
913 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
914 status: OCPP16DiagnosticsStatus
.UploadFailed
,
919 return this.handleIncomingRequestError(
920 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
922 { errorResponse
: Constants
.OCPP_RESPONSE_EMPTY
}
927 `${this.chargingStation.logPrefix()} Unsupported protocol ${
929 } to transfer the diagnostic logs archive`
931 await this.chargingStation
.ocppRequestService
.requestHandler
<
932 DiagnosticsStatusNotificationRequest
,
933 DiagnosticsStatusNotificationResponse
934 >(OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, {
935 status: OCPP16DiagnosticsStatus
.UploadFailed
,
937 return Constants
.OCPP_RESPONSE_EMPTY
;
941 private handleRequestTriggerMessage(
942 commandPayload
: OCPP16TriggerMessageRequest
943 ): OCPP16TriggerMessageResponse
{
945 !OCPP16ServiceUtils
.checkFeatureProfile(
946 this.chargingStation
,
947 OCPP16SupportedFeatureProfiles
.RemoteTrigger
,
948 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
951 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
954 switch (commandPayload
.requestedMessage
) {
955 case MessageTrigger
.BootNotification
:
957 this.chargingStation
.ocppRequestService
958 .requestHandler
<OCPP16BootNotificationRequest
, OCPP16BootNotificationResponse
>(
959 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
962 this.chargingStation
.getBootNotificationRequest().chargePointModel
,
964 this.chargingStation
.getBootNotificationRequest().chargePointVendor
,
965 chargeBoxSerialNumber
:
966 this.chargingStation
.getBootNotificationRequest().chargeBoxSerialNumber
,
968 this.chargingStation
.getBootNotificationRequest().firmwareVersion
,
969 chargePointSerialNumber
:
970 this.chargingStation
.getBootNotificationRequest().chargePointSerialNumber
,
971 iccid
: this.chargingStation
.getBootNotificationRequest().iccid
,
972 imsi
: this.chargingStation
.getBootNotificationRequest().imsi
,
974 this.chargingStation
.getBootNotificationRequest().meterSerialNumber
,
975 meterType
: this.chargingStation
.getBootNotificationRequest().meterType
,
977 { skipBufferingOnError
: true, triggerMessage
: true }
980 this.chargingStation
.bootNotificationResponse
= value
;
983 /* This is intentional */
985 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
986 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
987 case MessageTrigger
.Heartbeat
:
989 this.chargingStation
.ocppRequestService
990 .requestHandler
<OCPP16HeartbeatRequest
, OCPP16HeartbeatResponse
>(
991 OCPP16RequestCommand
.HEARTBEAT
,
994 triggerMessage
: true,
998 /* This is intentional */
1000 }, Constants
.OCPP_TRIGGER_MESSAGE_DELAY
);
1001 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
;
1003 return Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
;
1006 return this.handleIncomingRequestError(
1007 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
1009 { errorResponse
: Constants
.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
}