1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
4 import path from
'path';
5 import { fileURLToPath
} from
'url';
7 import type { JSONSchemaType
} from
'ajv';
9 import OCPPError from
'../../../exception/OCPPError';
10 import type { JsonObject
, JsonType
} from
'../../../types/JsonType';
11 import { OCPP16ChargePointErrorCode
} from
'../../../types/ocpp/1.6/ChargePointErrorCode';
12 import { OCPP16ChargePointStatus
} from
'../../../types/ocpp/1.6/ChargePointStatus';
13 import { OCPP16StandardParametersKey
} from
'../../../types/ocpp/1.6/Configuration';
15 OCPP16MeterValuesRequest
,
16 OCPP16MeterValuesResponse
,
17 } from
'../../../types/ocpp/1.6/MeterValues';
19 type OCPP16BootNotificationRequest
,
21 type OCPP16StatusNotificationRequest
,
22 } from
'../../../types/ocpp/1.6/Requests';
24 DiagnosticsStatusNotificationResponse
,
25 OCPP16BootNotificationResponse
,
26 OCPP16DataTransferResponse
,
27 OCPP16HeartbeatResponse
,
28 OCPP16StatusNotificationResponse
,
29 } from
'../../../types/ocpp/1.6/Responses';
31 OCPP16AuthorizationStatus
,
32 type OCPP16AuthorizeRequest
,
33 type OCPP16AuthorizeResponse
,
34 type OCPP16StartTransactionRequest
,
35 type OCPP16StartTransactionResponse
,
36 type OCPP16StopTransactionRequest
,
37 type OCPP16StopTransactionResponse
,
38 } from
'../../../types/ocpp/1.6/Transaction';
39 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
40 import { OCPPVersion
} from
'../../../types/ocpp/OCPPVersion';
41 import { RegistrationStatusEnumType
, type ResponseHandler
} from
'../../../types/ocpp/Responses';
42 import Constants from
'../../../utils/Constants';
43 import logger from
'../../../utils/Logger';
44 import Utils from
'../../../utils/Utils';
45 import type ChargingStation from
'../../ChargingStation';
46 import { ChargingStationConfigurationUtils
} from
'../../ChargingStationConfigurationUtils';
47 import OCPPResponseService from
'../OCPPResponseService';
48 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
50 const moduleName
= 'OCPP16ResponseService';
52 export default class OCPP16ResponseService
extends OCPPResponseService
{
53 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
54 private jsonSchemas
: Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>;
56 public constructor() {
57 if (new.target
?.name
=== moduleName
) {
58 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
60 super(OCPPVersion
.VERSION_16
);
61 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
62 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
63 [OCPP16RequestCommand
.HEARTBEAT
, this.emptyResponseHandler
.bind(this)],
64 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
65 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
66 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
67 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
68 [OCPP16RequestCommand
.METER_VALUES
, this.emptyResponseHandler
.bind(this)],
69 [OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
70 [OCPP16RequestCommand
.DATA_TRANSFER
, this.emptyResponseHandler
.bind(this)],
72 this.jsonSchemas
= new Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>([
74 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
78 path
.dirname(fileURLToPath(import.meta
.url
)),
79 '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
83 ) as JSONSchemaType
<OCPP16BootNotificationResponse
>,
86 OCPP16RequestCommand
.HEARTBEAT
,
90 path
.dirname(fileURLToPath(import.meta
.url
)),
91 '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
95 ) as JSONSchemaType
<OCPP16HeartbeatResponse
>,
98 OCPP16RequestCommand
.AUTHORIZE
,
102 path
.dirname(fileURLToPath(import.meta
.url
)),
103 '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
107 ) as JSONSchemaType
<OCPP16AuthorizeResponse
>,
110 OCPP16RequestCommand
.START_TRANSACTION
,
114 path
.dirname(fileURLToPath(import.meta
.url
)),
115 '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
119 ) as JSONSchemaType
<OCPP16StartTransactionResponse
>,
122 OCPP16RequestCommand
.STOP_TRANSACTION
,
126 path
.dirname(fileURLToPath(import.meta
.url
)),
127 '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
131 ) as JSONSchemaType
<OCPP16StopTransactionResponse
>,
134 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
138 path
.dirname(fileURLToPath(import.meta
.url
)),
139 '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
143 ) as JSONSchemaType
<OCPP16StatusNotificationResponse
>,
146 OCPP16RequestCommand
.METER_VALUES
,
150 path
.dirname(fileURLToPath(import.meta
.url
)),
151 '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
155 ) as JSONSchemaType
<OCPP16MeterValuesResponse
>,
158 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
162 path
.dirname(fileURLToPath(import.meta
.url
)),
163 '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json'
167 ) as JSONSchemaType
<DiagnosticsStatusNotificationResponse
>,
170 OCPP16RequestCommand
.DATA_TRANSFER
,
174 path
.dirname(fileURLToPath(import.meta
.url
)),
175 '../../../assets/json-schemas/ocpp/1.6/DataTransferResponse.json'
179 ) as JSONSchemaType
<OCPP16DataTransferResponse
>,
182 this.validatePayload
.bind(this);
185 public async responseHandler(
186 chargingStation
: ChargingStation
,
187 commandName
: OCPP16RequestCommand
,
189 requestPayload
: JsonType
192 chargingStation
.isRegistered() === true ||
193 commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
196 this.responseHandlers
.has(commandName
) === true &&
197 OCPP16ServiceUtils
.isRequestCommandSupported(chargingStation
, commandName
) === true
200 this.validatePayload(chargingStation
, commandName
, payload
);
201 await this.responseHandlers
.get(commandName
)(chargingStation
, payload
, requestPayload
);
204 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
212 ErrorType
.NOT_IMPLEMENTED
,
213 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
224 ErrorType
.SECURITY_ERROR
,
225 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
229 )} while the charging station is not registered on the central server.`,
236 private validatePayload(
237 chargingStation
: ChargingStation
,
238 commandName
: OCPP16RequestCommand
,
241 if (this.jsonSchemas
.has(commandName
) === true) {
242 return this.validateResponsePayload(
245 this.jsonSchemas
.get(commandName
),
250 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
255 private handleResponseBootNotification(
256 chargingStation
: ChargingStation
,
257 payload
: OCPP16BootNotificationResponse
259 if (payload
.status === RegistrationStatusEnumType
.ACCEPTED
) {
260 ChargingStationConfigurationUtils
.addConfigurationKey(
262 OCPP16StandardParametersKey
.HeartbeatInterval
,
263 payload
.interval
.toString(),
265 { overwrite
: true, save
: true }
267 ChargingStationConfigurationUtils
.addConfigurationKey(
269 OCPP16StandardParametersKey
.HeartBeatInterval
,
270 payload
.interval
.toString(),
272 { overwrite
: true, save
: true }
274 chargingStation
.heartbeatSetInterval
275 ? chargingStation
.restartHeartbeat()
276 : chargingStation
.startHeartbeat();
278 if (Object.values(RegistrationStatusEnumType
).includes(payload
.status)) {
279 const logMsg
= `${chargingStation.logPrefix()} Charging station in '${
281 }' state on the central server`;
282 payload
.status === RegistrationStatusEnumType
.REJECTED
283 ? logger
.warn(logMsg
)
284 : logger
.info(logMsg
);
287 chargingStation
.logPrefix() +
288 ' Charging station boot notification response received: %j with undefined registration status',
294 private handleResponseAuthorize(
295 chargingStation
: ChargingStation
,
296 payload
: OCPP16AuthorizeResponse
,
297 requestPayload
: OCPP16AuthorizeRequest
299 let authorizeConnectorId
: number;
300 for (const connectorId
of chargingStation
.connectors
.keys()) {
303 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
=== requestPayload
.idTag
305 authorizeConnectorId
= connectorId
;
309 const isAuthorizeConnectorIdDefined
= authorizeConnectorId
!== undefined;
310 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
311 isAuthorizeConnectorIdDefined
&&
312 (chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true);
314 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' accepted${
315 isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}
` : ''
319 if (isAuthorizeConnectorIdDefined
) {
320 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
321 delete chargingStation
.getConnectorStatus(authorizeConnectorId
).authorizeIdTag
;
324 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' rejected with status '${
325 payload.idTagInfo.status
326 }'${isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''}`
331 private async handleResponseStartTransaction(
332 chargingStation: ChargingStation,
333 payload: OCPP16StartTransactionResponse,
334 requestPayload: OCPP16StartTransactionRequest
336 const connectorId = requestPayload.connectorId;
338 let transactionConnectorId: number;
339 for (const id of chargingStation.connectors.keys()) {
340 if (id > 0 && id === connectorId) {
341 transactionConnectorId = id;
345 if (!transactionConnectorId) {
347 chargingStation.logPrefix() +
348 ' Trying to start a transaction on a non existing connector Id ' +
349 connectorId.toString()
354 chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
355 chargingStation.getAuthorizeRemoteTxRequests() === true &&
356 chargingStation.getLocalAuthListEnabled() === true &&
357 chargingStation.hasAuthorizedTags() &&
358 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false
361 chargingStation.logPrefix() +
362 ' Trying to start a transaction with a not local authorized idTag ' +
363 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
364 ' on connector Id ' +
365 connectorId.toString()
367 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
371 chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
372 chargingStation.getAuthorizeRemoteTxRequests() === true &&
373 chargingStation.getMustAuthorizeAtRemoteStart() === true &&
374 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false &&
375 chargingStation.getConnectorStatus(connectorId).idTagAuthorized === false
378 chargingStation.logPrefix() +
379 ' Trying to start a transaction with a not authorized idTag ' +
380 chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
381 ' on connector Id ' +
382 connectorId.toString()
384 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
388 chargingStation.getConnectorStatus(connectorId).idTagAuthorized &&
389 chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag
392 chargingStation.logPrefix() +
393 ' Trying to start a transaction with an idTag ' +
394 requestPayload.idTag +
395 ' different from the authorize request one ' +
396 chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
397 ' on connector Id ' +
398 connectorId.toString()
400 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
404 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
405 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !== requestPayload.idTag
408 chargingStation.logPrefix() +
409 ' Trying to start a transaction with an idTag ' +
410 requestPayload.idTag +
411 ' different from the local authorized one ' +
412 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
413 ' on connector Id ' +
414 connectorId.toString()
416 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
419 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
421 chargingStation.logPrefix() +
422 ' Trying to start a transaction on an already used connector ' +
423 connectorId.toString() +
425 chargingStation.getConnectorStatus(connectorId)
430 chargingStation.getConnectorStatus(connectorId)?.status !==
431 OCPP16ChargePointStatus.AVAILABLE &&
432 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.PREPARING
435 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()}
with status $
{
436 chargingStation
.getConnectorStatus(connectorId
)?.status
441 // if (!Number.isInteger(payload.transactionId)) {
443 // `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()}
with a non integer transaction Id $
{
444 // payload.transactionId
445 // }, converting to integer`
447 // payload.transactionId = Utils.convertToInt(payload.transactionId);
450 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
451 chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
452 chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
453 chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
454 chargingStation
.getConnectorStatus(
456 ).transactionEnergyActiveImportRegisterValue
= 0;
457 chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
=
458 OCPP16ServiceUtils
.buildTransactionBeginMeterValue(
461 requestPayload
.meterStart
463 chargingStation
.getBeginEndMeterValues() &&
464 (await chargingStation
.ocppRequestService
.requestHandler
<
465 OCPP16MeterValuesRequest
,
466 OCPP16MeterValuesResponse
467 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
469 transactionId
: payload
.transactionId
,
470 meterValue
: [chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
],
472 await chargingStation
.ocppRequestService
.requestHandler
<
473 OCPP16StatusNotificationRequest
,
474 OCPP16StatusNotificationResponse
475 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
477 status: OCPP16ChargePointStatus
.CHARGING
,
478 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
480 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.CHARGING
;
482 chargingStation
.logPrefix() +
484 payload
.transactionId
.toString() +
486 chargingStation
.stationInfo
.chargingStationId
+
488 connectorId
.toString() +
490 requestPayload
.idTag
+
493 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
494 chargingStation
.powerDivider
++;
496 const configuredMeterValueSampleInterval
=
497 ChargingStationConfigurationUtils
.getConfigurationKey(
499 OCPP16StandardParametersKey
.MeterValueSampleInterval
501 chargingStation
.startMeterValues(
503 configuredMeterValueSampleInterval
504 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
505 : Constants
.DEFAULT_METER_VALUES_INTERVAL
509 chargingStation
.logPrefix() +
510 ' Starting transaction id ' +
511 payload
.transactionId
.toString() +
512 " REJECTED with status '" +
513 payload
?.idTagInfo
?.status +
515 requestPayload
.idTag
+
518 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
522 private async resetConnectorOnStartTransactionError(
523 chargingStation
: ChargingStation
,
526 chargingStation
.resetConnectorStatus(connectorId
);
528 chargingStation
.getConnectorStatus(connectorId
).status !== OCPP16ChargePointStatus
.AVAILABLE
530 await chargingStation
.ocppRequestService
.requestHandler
<
531 OCPP16StatusNotificationRequest
,
532 OCPP16StatusNotificationResponse
533 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
535 status: OCPP16ChargePointStatus
.AVAILABLE
,
536 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
538 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
542 private async handleResponseStopTransaction(
543 chargingStation
: ChargingStation
,
544 payload
: OCPP16StopTransactionResponse
,
545 requestPayload
: OCPP16StopTransactionRequest
547 const transactionConnectorId
= chargingStation
.getConnectorIdByTransactionId(
548 requestPayload
.transactionId
550 if (!transactionConnectorId
) {
552 chargingStation
.logPrefix() +
553 ' Trying to stop a non existing transaction ' +
554 requestPayload
.transactionId
.toString()
558 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
559 chargingStation
.getBeginEndMeterValues() === true &&
560 chargingStation
.getOcppStrictCompliance() === false &&
561 chargingStation
.getOutOfOrderEndMeterValues() === true &&
562 (await chargingStation
.ocppRequestService
.requestHandler
<
563 OCPP16MeterValuesRequest
,
564 OCPP16MeterValuesResponse
565 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
566 connectorId
: transactionConnectorId
,
567 transactionId
: requestPayload
.transactionId
,
569 OCPP16ServiceUtils
.buildTransactionEndMeterValue(
571 transactionConnectorId
,
572 requestPayload
.meterStop
577 chargingStation
.isChargingStationAvailable() === false ||
578 chargingStation
.isConnectorAvailable(transactionConnectorId
) === false
580 await chargingStation
.ocppRequestService
.requestHandler
<
581 OCPP16StatusNotificationRequest
,
582 OCPP16StatusNotificationResponse
583 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
584 connectorId
: transactionConnectorId
,
585 status: OCPP16ChargePointStatus
.UNAVAILABLE
,
586 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
588 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
589 OCPP16ChargePointStatus
.UNAVAILABLE
;
591 await chargingStation
.ocppRequestService
.requestHandler
<
592 OCPP16BootNotificationRequest
,
593 OCPP16BootNotificationResponse
594 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
595 connectorId
: transactionConnectorId
,
596 status: OCPP16ChargePointStatus
.AVAILABLE
,
597 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
599 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
600 OCPP16ChargePointStatus
.AVAILABLE
;
602 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
603 chargingStation
.powerDivider
--;
605 chargingStation
.resetConnectorStatus(transactionConnectorId
);
607 chargingStation
.logPrefix() +
609 requestPayload
.transactionId
.toString() +
611 chargingStation
.stationInfo
.chargingStationId
+
613 transactionConnectorId
.toString()
617 chargingStation
.logPrefix() +
618 ' Stopping transaction id ' +
619 requestPayload
.transactionId
.toString() +
620 " REJECTED with status '" +
621 payload
.idTagInfo
?.status +