1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
4 import path from
'path';
5 import { fileURLToPath
} from
'url';
7 import { JSONSchemaType
} from
'ajv';
9 import OCPPError from
'../../../exception/OCPPError';
10 import { 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 OCPP16BootNotificationRequest
,
21 OCPP16StatusNotificationRequest
,
22 } from
'../../../types/ocpp/1.6/Requests';
24 OCPP16BootNotificationResponse
,
25 OCPP16HeartbeatResponse
,
26 OCPP16RegistrationStatus
,
27 OCPP16StatusNotificationResponse
,
28 } from
'../../../types/ocpp/1.6/Responses';
30 OCPP16AuthorizationStatus
,
31 OCPP16AuthorizeRequest
,
32 OCPP16AuthorizeResponse
,
33 OCPP16StartTransactionRequest
,
34 OCPP16StartTransactionResponse
,
35 OCPP16StopTransactionRequest
,
36 OCPP16StopTransactionResponse
,
37 } from
'../../../types/ocpp/1.6/Transaction';
38 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
39 import { ResponseHandler
} from
'../../../types/ocpp/Responses';
40 import logger from
'../../../utils/Logger';
41 import Utils from
'../../../utils/Utils';
42 import type ChargingStation from
'../../ChargingStation';
43 import { ChargingStationConfigurationUtils
} from
'../../ChargingStationConfigurationUtils';
44 import { ChargingStationUtils
} from
'../../ChargingStationUtils';
45 import OCPPResponseService from
'../OCPPResponseService';
46 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
48 const moduleName
= 'OCPP16ResponseService';
50 export default class OCPP16ResponseService
extends OCPPResponseService
{
51 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
52 private bootNotificationResponseJsonSchema
: JSONSchemaType
<OCPP16BootNotificationResponse
>;
53 private heartbeatResponseJsonSchema
: JSONSchemaType
<OCPP16HeartbeatResponse
>;
54 private authorizeResponseJsonSchema
: JSONSchemaType
<OCPP16AuthorizeResponse
>;
55 private startTransactionResponseJsonSchema
: JSONSchemaType
<OCPP16StartTransactionResponse
>;
56 private stopTransactionResponseJsonSchema
: JSONSchemaType
<OCPP16StopTransactionResponse
>;
57 private statusNotificationResponseJsonSchema
: JSONSchemaType
<OCPP16StatusNotificationResponse
>;
58 private meterValuesResponseJsonSchema
: JSONSchemaType
<OCPP16MeterValuesResponse
>;
60 public constructor() {
61 if (new.target
?.name
=== moduleName
) {
62 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
65 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
66 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
67 [OCPP16RequestCommand
.HEARTBEAT
, this.handleResponseHeartbeat
.bind(this)],
68 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
69 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
70 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
71 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.handleResponseStatusNotification
.bind(this)],
72 [OCPP16RequestCommand
.METER_VALUES
, this.handleResponseMeterValues
.bind(this)],
74 this.bootNotificationResponseJsonSchema
= JSON
.parse(
77 path
.dirname(fileURLToPath(import.meta
.url
)),
78 '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
82 ) as JSONSchemaType
<OCPP16BootNotificationResponse
>;
83 this.heartbeatResponseJsonSchema
= JSON
.parse(
86 path
.dirname(fileURLToPath(import.meta
.url
)),
87 '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
91 ) as JSONSchemaType
<OCPP16HeartbeatResponse
>;
92 this.authorizeResponseJsonSchema
= JSON
.parse(
95 path
.dirname(fileURLToPath(import.meta
.url
)),
96 '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
100 ) as JSONSchemaType
<OCPP16AuthorizeResponse
>;
101 this.startTransactionResponseJsonSchema
= JSON
.parse(
104 path
.dirname(fileURLToPath(import.meta
.url
)),
105 '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
109 ) as JSONSchemaType
<OCPP16StartTransactionResponse
>;
110 this.stopTransactionResponseJsonSchema
= JSON
.parse(
113 path
.dirname(fileURLToPath(import.meta
.url
)),
114 '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
118 ) as JSONSchemaType
<OCPP16StopTransactionResponse
>;
119 this.statusNotificationResponseJsonSchema
= JSON
.parse(
122 path
.dirname(fileURLToPath(import.meta
.url
)),
123 '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
127 ) as JSONSchemaType
<OCPP16StatusNotificationResponse
>;
128 this.meterValuesResponseJsonSchema
= JSON
.parse(
131 path
.dirname(fileURLToPath(import.meta
.url
)),
132 '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
136 ) as JSONSchemaType
<OCPP16MeterValuesResponse
>;
139 public async responseHandler(
140 chargingStation
: ChargingStation
,
141 commandName
: OCPP16RequestCommand
,
143 requestPayload
: JsonType
145 if (chargingStation
.isRegistered() || commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
) {
147 this.responseHandlers
.has(commandName
) &&
148 ChargingStationUtils
.isRequestCommandSupported(commandName
, chargingStation
)
151 await this.responseHandlers
.get(commandName
)(chargingStation
, payload
, requestPayload
);
153 logger
.error(chargingStation
.logPrefix() + ' Handle request response error: %j', error
);
159 ErrorType
.NOT_IMPLEMENTED
,
160 `${commandName} is not implemented to handle request response PDU ${JSON.stringify(
171 ErrorType
.SECURITY_ERROR
,
172 `${commandName} cannot be issued to handle request response PDU ${JSON.stringify(
176 )} while the charging station is not registered on the central server. `,
183 private handleResponseBootNotification(
184 chargingStation
: ChargingStation
,
185 payload
: OCPP16BootNotificationResponse
187 this.validateResponsePayload(
189 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
190 this.bootNotificationResponseJsonSchema
,
193 if (payload
.status === OCPP16RegistrationStatus
.ACCEPTED
) {
194 ChargingStationConfigurationUtils
.addConfigurationKey(
196 OCPP16StandardParametersKey
.HeartbeatInterval
,
197 payload
.interval
.toString(),
199 { overwrite
: true, save
: true }
201 ChargingStationConfigurationUtils
.addConfigurationKey(
203 OCPP16StandardParametersKey
.HeartBeatInterval
,
204 payload
.interval
.toString(),
206 { overwrite
: true, save
: true }
208 chargingStation
.heartbeatSetInterval
209 ? chargingStation
.restartHeartbeat()
210 : chargingStation
.startHeartbeat();
212 if (Object.values(OCPP16RegistrationStatus
).includes(payload
.status)) {
213 const logMsg
= `${chargingStation.logPrefix()} Charging station in '${
215 }' state on the central server`;
216 payload
.status === OCPP16RegistrationStatus
.REJECTED
217 ? logger
.warn(logMsg
)
218 : logger
.info(logMsg
);
221 chargingStation
.logPrefix() +
222 ' Charging station boot notification response received: %j with undefined registration status',
228 private handleResponseHeartbeat(
229 chargingStation
: ChargingStation
,
230 payload
: OCPP16HeartbeatResponse
232 this.validateResponsePayload(
234 OCPP16RequestCommand
.HEARTBEAT
,
235 this.heartbeatResponseJsonSchema
,
240 private handleResponseAuthorize(
241 chargingStation
: ChargingStation
,
242 payload
: OCPP16AuthorizeResponse
,
243 requestPayload
: OCPP16AuthorizeRequest
245 this.validateResponsePayload(
247 OCPP16RequestCommand
.AUTHORIZE
,
248 this.authorizeResponseJsonSchema
,
251 let authorizeConnectorId
: number;
252 for (const connectorId
of chargingStation
.connectors
.keys()) {
255 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
=== requestPayload
.idTag
257 authorizeConnectorId
= connectorId
;
261 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
262 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true;
264 `${chargingStation.logPrefix()} IdTag ${
266 } authorized on connector ${authorizeConnectorId}`
269 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
270 delete chargingStation
.getConnectorStatus(authorizeConnectorId
).authorizeIdTag
;
272 `${chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status '${
273 payload.idTagInfo.status
274 }' on connector ${authorizeConnectorId}`
279 private async handleResponseStartTransaction(
280 chargingStation
: ChargingStation
,
281 payload
: OCPP16StartTransactionResponse
,
282 requestPayload
: OCPP16StartTransactionRequest
284 this.validateResponsePayload(
286 OCPP16RequestCommand
.START_TRANSACTION
,
287 this.startTransactionResponseJsonSchema
,
290 const connectorId
= requestPayload
.connectorId
;
292 let transactionConnectorId
: number;
293 for (const id
of chargingStation
.connectors
.keys()) {
294 if (id
> 0 && id
=== connectorId
) {
295 transactionConnectorId
= id
;
299 if (!transactionConnectorId
) {
301 chargingStation
.logPrefix() +
302 ' Trying to start a transaction on a non existing connector Id ' +
303 connectorId
.toString()
308 chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
&&
309 chargingStation
.getAuthorizeRemoteTxRequests() &&
310 chargingStation
.getLocalAuthListEnabled() &&
311 chargingStation
.hasAuthorizedTags() &&
312 !chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
315 chargingStation
.logPrefix() +
316 ' Trying to start a transaction with a not local authorized idTag ' +
317 chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+
318 ' on connector Id ' +
319 connectorId
.toString()
321 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
325 chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
&&
326 chargingStation
.getAuthorizeRemoteTxRequests() &&
327 chargingStation
.getMayAuthorizeAtRemoteStart() &&
328 !chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
&&
329 !chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
332 chargingStation
.logPrefix() +
333 ' Trying to start a transaction with a not authorized idTag ' +
334 chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+
335 ' on connector Id ' +
336 connectorId
.toString()
338 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
342 chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
&&
343 chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
!== requestPayload
.idTag
346 chargingStation
.logPrefix() +
347 ' Trying to start a transaction with an idTag ' +
348 requestPayload
.idTag
+
349 ' different from the authorize request one ' +
350 chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+
351 ' on connector Id ' +
352 connectorId
.toString()
354 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
358 chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
&&
359 chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
!== requestPayload
.idTag
362 chargingStation
.logPrefix() +
363 ' Trying to start a transaction with an idTag ' +
364 requestPayload
.idTag
+
365 ' different from the local authorized one ' +
366 chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+
367 ' on connector Id ' +
368 connectorId
.toString()
370 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
373 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
375 chargingStation
.logPrefix() +
376 ' Trying to start a transaction on an already used connector ' +
377 connectorId
.toString() +
379 chargingStation
.getConnectorStatus(connectorId
)
384 chargingStation
.getConnectorStatus(connectorId
)?.status !==
385 OCPP16ChargePointStatus
.AVAILABLE
&&
386 chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.PREPARING
389 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
390 chargingStation.getConnectorStatus(connectorId)?.status
395 if (!Number.isInteger(payload
.transactionId
)) {
397 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
398 payload.transactionId
399 }, converting to integer`
401 payload
.transactionId
= Utils
.convertToInt(payload
.transactionId
);
404 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
405 chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
406 chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
407 chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
408 chargingStation
.getConnectorStatus(
410 ).transactionEnergyActiveImportRegisterValue
= 0;
411 chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
=
412 OCPP16ServiceUtils
.buildTransactionBeginMeterValue(
415 requestPayload
.meterStart
417 chargingStation
.getBeginEndMeterValues() &&
418 (await chargingStation
.ocppRequestService
.requestHandler
<
419 OCPP16MeterValuesRequest
,
420 OCPP16MeterValuesResponse
421 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
423 transactionId
: payload
.transactionId
,
424 meterValue
: [chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
],
426 await chargingStation
.ocppRequestService
.requestHandler
<
427 OCPP16StatusNotificationRequest
,
428 OCPP16StatusNotificationResponse
429 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
431 status: OCPP16ChargePointStatus
.CHARGING
,
432 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
434 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.CHARGING
;
436 chargingStation
.logPrefix() +
438 payload
.transactionId
.toString() +
440 chargingStation
.stationInfo
.chargingStationId
+
442 connectorId
.toString() +
446 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
447 chargingStation
.powerDivider
++;
449 const configuredMeterValueSampleInterval
=
450 ChargingStationConfigurationUtils
.getConfigurationKey(
452 OCPP16StandardParametersKey
.MeterValueSampleInterval
454 chargingStation
.startMeterValues(
456 configuredMeterValueSampleInterval
457 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
462 chargingStation
.logPrefix() +
463 ' Starting transaction id ' +
464 payload
.transactionId
.toString() +
465 " REJECTED with status '" +
466 payload
?.idTagInfo
?.status +
470 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
474 private async resetConnectorOnStartTransactionError(
475 chargingStation
: ChargingStation
,
478 chargingStation
.resetConnectorStatus(connectorId
);
480 chargingStation
.getConnectorStatus(connectorId
).status !== OCPP16ChargePointStatus
.AVAILABLE
482 await chargingStation
.ocppRequestService
.requestHandler
<
483 OCPP16StatusNotificationRequest
,
484 OCPP16StatusNotificationResponse
485 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
487 status: OCPP16ChargePointStatus
.AVAILABLE
,
488 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
490 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
494 private async handleResponseStopTransaction(
495 chargingStation
: ChargingStation
,
496 payload
: OCPP16StopTransactionResponse
,
497 requestPayload
: OCPP16StopTransactionRequest
499 this.validateResponsePayload(
501 OCPP16RequestCommand
.STOP_TRANSACTION
,
502 this.stopTransactionResponseJsonSchema
,
505 const transactionConnectorId
= chargingStation
.getConnectorIdByTransactionId(
506 requestPayload
.transactionId
508 if (!transactionConnectorId
) {
510 chargingStation
.logPrefix() +
511 ' Trying to stop a non existing transaction ' +
512 requestPayload
.transactionId
.toString()
516 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
517 chargingStation
.getBeginEndMeterValues() &&
518 !chargingStation
.getOcppStrictCompliance() &&
519 chargingStation
.getOutOfOrderEndMeterValues() &&
520 (await chargingStation
.ocppRequestService
.requestHandler
<
521 OCPP16MeterValuesRequest
,
522 OCPP16MeterValuesResponse
523 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
524 connectorId
: transactionConnectorId
,
525 transactionId
: requestPayload
.transactionId
,
527 OCPP16ServiceUtils
.buildTransactionEndMeterValue(
529 transactionConnectorId
,
530 requestPayload
.meterStop
535 !chargingStation
.isChargingStationAvailable() ||
536 !chargingStation
.isConnectorAvailable(transactionConnectorId
)
538 await chargingStation
.ocppRequestService
.requestHandler
<
539 OCPP16StatusNotificationRequest
,
540 OCPP16StatusNotificationResponse
541 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
542 connectorId
: transactionConnectorId
,
543 status: OCPP16ChargePointStatus
.UNAVAILABLE
,
544 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
546 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
547 OCPP16ChargePointStatus
.UNAVAILABLE
;
549 await chargingStation
.ocppRequestService
.requestHandler
<
550 OCPP16BootNotificationRequest
,
551 OCPP16BootNotificationResponse
552 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
553 connectorId
: transactionConnectorId
,
554 status: OCPP16ChargePointStatus
.AVAILABLE
,
555 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
557 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
558 OCPP16ChargePointStatus
.AVAILABLE
;
560 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
561 chargingStation
.powerDivider
--;
564 chargingStation
.logPrefix() +
566 requestPayload
.transactionId
.toString() +
568 chargingStation
.stationInfo
.chargingStationId
+
570 transactionConnectorId
.toString()
572 chargingStation
.resetConnectorStatus(transactionConnectorId
);
575 chargingStation
.logPrefix() +
576 ' Stopping transaction id ' +
577 requestPayload
.transactionId
.toString() +
578 " REJECTED with status '" +
579 payload
.idTagInfo
?.status +
585 private handleResponseStatusNotification(
586 chargingStation
: ChargingStation
,
587 payload
: OCPP16StatusNotificationResponse
589 this.validateResponsePayload(
591 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
592 this.statusNotificationResponseJsonSchema
,
597 private handleResponseMeterValues(
598 chargingStation
: ChargingStation
,
599 payload
: OCPP16MeterValuesResponse
601 this.validateResponsePayload(
603 OCPP16RequestCommand
.METER_VALUES
,
604 this.meterValuesResponseJsonSchema
,