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
,
20 OCPP16IncomingRequestCommand
,
22 type OCPP16StatusNotificationRequest
,
23 } from
'../../../types/ocpp/1.6/Requests';
25 DiagnosticsStatusNotificationResponse
,
26 OCPP16BootNotificationResponse
,
27 OCPP16DataTransferResponse
,
28 OCPP16HeartbeatResponse
,
29 OCPP16StatusNotificationResponse
,
30 } from
'../../../types/ocpp/1.6/Responses';
32 OCPP16AuthorizationStatus
,
33 type OCPP16AuthorizeRequest
,
34 type OCPP16AuthorizeResponse
,
35 type OCPP16StartTransactionRequest
,
36 type OCPP16StartTransactionResponse
,
37 type OCPP16StopTransactionRequest
,
38 type OCPP16StopTransactionResponse
,
39 } from
'../../../types/ocpp/1.6/Transaction';
40 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
41 import { OCPPVersion
} from
'../../../types/ocpp/OCPPVersion';
42 import { RegistrationStatusEnumType
, type ResponseHandler
} from
'../../../types/ocpp/Responses';
43 import Constants from
'../../../utils/Constants';
44 import logger from
'../../../utils/Logger';
45 import Utils from
'../../../utils/Utils';
46 import type ChargingStation from
'../../ChargingStation';
47 import { ChargingStationConfigurationUtils
} from
'../../ChargingStationConfigurationUtils';
48 import OCPPResponseService from
'../OCPPResponseService';
49 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
51 const moduleName
= 'OCPP16ResponseService';
53 export default class OCPP16ResponseService
extends OCPPResponseService
{
54 public jsonIncomingRequestResponseSchemas
: Map
<
55 OCPP16IncomingRequestCommand
,
56 JSONSchemaType
<JsonObject
>
59 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
60 private jsonSchemas
: Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>;
62 public constructor() {
63 if (new.target
?.name
=== moduleName
) {
64 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
66 super(OCPPVersion
.VERSION_16
);
67 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
68 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
69 [OCPP16RequestCommand
.HEARTBEAT
, this.emptyResponseHandler
.bind(this)],
70 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
71 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
72 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
73 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
74 [OCPP16RequestCommand
.METER_VALUES
, this.emptyResponseHandler
.bind(this)],
75 [OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
76 [OCPP16RequestCommand
.DATA_TRANSFER
, this.emptyResponseHandler
.bind(this)],
78 this.jsonSchemas
= new Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>([
80 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
84 path
.dirname(fileURLToPath(import.meta
.url
)),
85 '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
89 ) as JSONSchemaType
<OCPP16BootNotificationResponse
>,
92 OCPP16RequestCommand
.HEARTBEAT
,
96 path
.dirname(fileURLToPath(import.meta
.url
)),
97 '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
101 ) as JSONSchemaType
<OCPP16HeartbeatResponse
>,
104 OCPP16RequestCommand
.AUTHORIZE
,
108 path
.dirname(fileURLToPath(import.meta
.url
)),
109 '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
113 ) as JSONSchemaType
<OCPP16AuthorizeResponse
>,
116 OCPP16RequestCommand
.START_TRANSACTION
,
120 path
.dirname(fileURLToPath(import.meta
.url
)),
121 '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
125 ) as JSONSchemaType
<OCPP16StartTransactionResponse
>,
128 OCPP16RequestCommand
.STOP_TRANSACTION
,
132 path
.dirname(fileURLToPath(import.meta
.url
)),
133 '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
137 ) as JSONSchemaType
<OCPP16StopTransactionResponse
>,
140 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
144 path
.dirname(fileURLToPath(import.meta
.url
)),
145 '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
149 ) as JSONSchemaType
<OCPP16StatusNotificationResponse
>,
152 OCPP16RequestCommand
.METER_VALUES
,
156 path
.dirname(fileURLToPath(import.meta
.url
)),
157 '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
161 ) as JSONSchemaType
<OCPP16MeterValuesResponse
>,
164 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
168 path
.dirname(fileURLToPath(import.meta
.url
)),
169 '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json'
173 ) as JSONSchemaType
<DiagnosticsStatusNotificationResponse
>,
176 OCPP16RequestCommand
.DATA_TRANSFER
,
180 path
.dirname(fileURLToPath(import.meta
.url
)),
181 '../../../assets/json-schemas/ocpp/1.6/DataTransferResponse.json'
185 ) as JSONSchemaType
<OCPP16DataTransferResponse
>,
188 this.jsonIncomingRequestResponseSchemas
= new Map();
189 this.validatePayload
.bind(this);
192 public async responseHandler(
193 chargingStation
: ChargingStation
,
194 commandName
: OCPP16RequestCommand
,
196 requestPayload
: JsonType
199 chargingStation
.isRegistered() === true ||
200 commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
203 this.responseHandlers
.has(commandName
) === true &&
204 OCPP16ServiceUtils
.isRequestCommandSupported(chargingStation
, commandName
) === true
207 this.validatePayload(chargingStation
, commandName
, payload
);
208 await this.responseHandlers
.get(commandName
)(chargingStation
, payload
, requestPayload
);
211 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
219 ErrorType
.NOT_IMPLEMENTED
,
220 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
231 ErrorType
.SECURITY_ERROR
,
232 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
236 )} while the charging station is not registered on the central server.`,
243 private validatePayload(
244 chargingStation
: ChargingStation
,
245 commandName
: OCPP16RequestCommand
,
248 if (this.jsonSchemas
.has(commandName
) === true) {
249 return this.validateResponsePayload(
252 this.jsonSchemas
.get(commandName
),
257 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
262 private handleResponseBootNotification(
263 chargingStation
: ChargingStation
,
264 payload
: OCPP16BootNotificationResponse
266 if (payload
.status === RegistrationStatusEnumType
.ACCEPTED
) {
267 ChargingStationConfigurationUtils
.addConfigurationKey(
269 OCPP16StandardParametersKey
.HeartbeatInterval
,
270 payload
.interval
.toString(),
272 { overwrite
: true, save
: true }
274 ChargingStationConfigurationUtils
.addConfigurationKey(
276 OCPP16StandardParametersKey
.HeartBeatInterval
,
277 payload
.interval
.toString(),
279 { overwrite
: true, save
: true }
281 chargingStation
.heartbeatSetInterval
282 ? chargingStation
.restartHeartbeat()
283 : chargingStation
.startHeartbeat();
285 if (Object.values(RegistrationStatusEnumType
).includes(payload
.status)) {
286 const logMsg
= `${chargingStation.logPrefix()} Charging station in '${
288 }' state on the central server`;
289 payload
.status === RegistrationStatusEnumType
.REJECTED
290 ? logger
.warn(logMsg
)
291 : logger
.info(logMsg
);
294 chargingStation
.logPrefix() +
295 ' Charging station boot notification response received: %j with undefined registration status',
301 private handleResponseAuthorize(
302 chargingStation
: ChargingStation
,
303 payload
: OCPP16AuthorizeResponse
,
304 requestPayload
: OCPP16AuthorizeRequest
306 let authorizeConnectorId
: number;
307 for (const connectorId
of chargingStation
.connectors
.keys()) {
310 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
=== requestPayload
.idTag
312 authorizeConnectorId
= connectorId
;
316 const isAuthorizeConnectorIdDefined
= authorizeConnectorId
!== undefined;
317 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
318 isAuthorizeConnectorIdDefined
&&
319 (chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true);
321 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' accepted${
322 isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}
` : ''
326 if (isAuthorizeConnectorIdDefined
) {
327 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
328 delete chargingStation
.getConnectorStatus(authorizeConnectorId
).authorizeIdTag
;
331 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' rejected with status '${
332 payload.idTagInfo.status
333 }'${isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''}`
338 private async handleResponseStartTransaction(
339 chargingStation: ChargingStation,
340 payload: OCPP16StartTransactionResponse,
341 requestPayload: OCPP16StartTransactionRequest
343 const connectorId = requestPayload.connectorId;
345 let transactionConnectorId: number;
346 for (const id of chargingStation.connectors.keys()) {
347 if (id > 0 && id === connectorId) {
348 transactionConnectorId = id;
352 if (!transactionConnectorId) {
354 chargingStation.logPrefix() +
355 ' Trying to start a transaction on a non existing connector Id ' +
356 connectorId.toString()
361 chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
362 chargingStation.getAuthorizeRemoteTxRequests() === true &&
363 chargingStation.getLocalAuthListEnabled() === true &&
364 chargingStation.hasAuthorizedTags() &&
365 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false
368 chargingStation.logPrefix() +
369 ' Trying to start a transaction with a not local authorized idTag ' +
370 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
371 ' on connector Id ' +
372 connectorId.toString()
374 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
378 chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
379 chargingStation.getAuthorizeRemoteTxRequests() === true &&
380 chargingStation.getMustAuthorizeAtRemoteStart() === true &&
381 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false &&
382 chargingStation.getConnectorStatus(connectorId).idTagAuthorized === false
385 chargingStation.logPrefix() +
386 ' Trying to start a transaction with a not authorized idTag ' +
387 chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
388 ' on connector Id ' +
389 connectorId.toString()
391 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
395 chargingStation.getConnectorStatus(connectorId).idTagAuthorized &&
396 chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag
399 chargingStation.logPrefix() +
400 ' Trying to start a transaction with an idTag ' +
401 requestPayload.idTag +
402 ' different from the authorize request one ' +
403 chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
404 ' on connector Id ' +
405 connectorId.toString()
407 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
411 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
412 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !== requestPayload.idTag
415 chargingStation.logPrefix() +
416 ' Trying to start a transaction with an idTag ' +
417 requestPayload.idTag +
418 ' different from the local authorized one ' +
419 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
420 ' on connector Id ' +
421 connectorId.toString()
423 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
426 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
428 chargingStation.logPrefix() +
429 ' Trying to start a transaction on an already used connector ' +
430 connectorId.toString() +
432 chargingStation.getConnectorStatus(connectorId)
437 chargingStation.getConnectorStatus(connectorId)?.status !==
438 OCPP16ChargePointStatus.AVAILABLE &&
439 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.PREPARING
442 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()}
with status $
{
443 chargingStation
.getConnectorStatus(connectorId
)?.status
448 // if (!Number.isInteger(payload.transactionId)) {
450 // `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()}
with a non integer transaction Id $
{
451 // payload.transactionId
452 // }, converting to integer`
454 // payload.transactionId = Utils.convertToInt(payload.transactionId);
457 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
458 chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
459 chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
460 chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
461 chargingStation
.getConnectorStatus(
463 ).transactionEnergyActiveImportRegisterValue
= 0;
464 chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
=
465 OCPP16ServiceUtils
.buildTransactionBeginMeterValue(
468 requestPayload
.meterStart
470 chargingStation
.getBeginEndMeterValues() &&
471 (await chargingStation
.ocppRequestService
.requestHandler
<
472 OCPP16MeterValuesRequest
,
473 OCPP16MeterValuesResponse
474 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
476 transactionId
: payload
.transactionId
,
477 meterValue
: [chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
],
479 await chargingStation
.ocppRequestService
.requestHandler
<
480 OCPP16StatusNotificationRequest
,
481 OCPP16StatusNotificationResponse
482 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
484 status: OCPP16ChargePointStatus
.CHARGING
,
485 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
487 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.CHARGING
;
489 chargingStation
.logPrefix() +
491 payload
.transactionId
.toString() +
493 chargingStation
.stationInfo
.chargingStationId
+
495 connectorId
.toString() +
497 requestPayload
.idTag
+
500 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
501 chargingStation
.powerDivider
++;
503 const configuredMeterValueSampleInterval
=
504 ChargingStationConfigurationUtils
.getConfigurationKey(
506 OCPP16StandardParametersKey
.MeterValueSampleInterval
508 chargingStation
.startMeterValues(
510 configuredMeterValueSampleInterval
511 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
512 : Constants
.DEFAULT_METER_VALUES_INTERVAL
516 chargingStation
.logPrefix() +
517 ' Starting transaction id ' +
518 payload
.transactionId
.toString() +
519 " REJECTED with status '" +
520 payload
?.idTagInfo
?.status +
522 requestPayload
.idTag
+
525 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
529 private async resetConnectorOnStartTransactionError(
530 chargingStation
: ChargingStation
,
533 chargingStation
.resetConnectorStatus(connectorId
);
535 chargingStation
.getConnectorStatus(connectorId
).status !== OCPP16ChargePointStatus
.AVAILABLE
537 await chargingStation
.ocppRequestService
.requestHandler
<
538 OCPP16StatusNotificationRequest
,
539 OCPP16StatusNotificationResponse
540 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
542 status: OCPP16ChargePointStatus
.AVAILABLE
,
543 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
545 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
549 private async handleResponseStopTransaction(
550 chargingStation
: ChargingStation
,
551 payload
: OCPP16StopTransactionResponse
,
552 requestPayload
: OCPP16StopTransactionRequest
554 const transactionConnectorId
= chargingStation
.getConnectorIdByTransactionId(
555 requestPayload
.transactionId
557 if (!transactionConnectorId
) {
559 chargingStation
.logPrefix() +
560 ' Trying to stop a non existing transaction ' +
561 requestPayload
.transactionId
.toString()
565 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
566 chargingStation
.getBeginEndMeterValues() === true &&
567 chargingStation
.getOcppStrictCompliance() === false &&
568 chargingStation
.getOutOfOrderEndMeterValues() === true &&
569 (await chargingStation
.ocppRequestService
.requestHandler
<
570 OCPP16MeterValuesRequest
,
571 OCPP16MeterValuesResponse
572 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
573 connectorId
: transactionConnectorId
,
574 transactionId
: requestPayload
.transactionId
,
576 OCPP16ServiceUtils
.buildTransactionEndMeterValue(
578 transactionConnectorId
,
579 requestPayload
.meterStop
584 chargingStation
.isChargingStationAvailable() === false ||
585 chargingStation
.isConnectorAvailable(transactionConnectorId
) === false
587 await chargingStation
.ocppRequestService
.requestHandler
<
588 OCPP16StatusNotificationRequest
,
589 OCPP16StatusNotificationResponse
590 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
591 connectorId
: transactionConnectorId
,
592 status: OCPP16ChargePointStatus
.UNAVAILABLE
,
593 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
595 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
596 OCPP16ChargePointStatus
.UNAVAILABLE
;
598 await chargingStation
.ocppRequestService
.requestHandler
<
599 OCPP16BootNotificationRequest
,
600 OCPP16BootNotificationResponse
601 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
602 connectorId
: transactionConnectorId
,
603 status: OCPP16ChargePointStatus
.AVAILABLE
,
604 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
606 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
607 OCPP16ChargePointStatus
.AVAILABLE
;
609 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
610 chargingStation
.powerDivider
--;
612 chargingStation
.resetConnectorStatus(transactionConnectorId
);
614 chargingStation
.logPrefix() +
616 requestPayload
.transactionId
.toString() +
618 chargingStation
.stationInfo
.chargingStationId
+
620 transactionConnectorId
.toString()
624 chargingStation
.logPrefix() +
625 ' Stopping transaction id ' +
626 requestPayload
.transactionId
.toString() +
627 " REJECTED with status '" +
628 payload
.idTagInfo
?.status +