1 // Partial Copyright Jerome Benoit. 2021. 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 OCPP16BootNotificationRequest
,
21 OCPP16StatusNotificationRequest
,
22 } from
'../../../types/ocpp/1.6/Requests';
24 DiagnosticsStatusNotificationResponse
,
25 OCPP16BootNotificationResponse
,
26 OCPP16HeartbeatResponse
,
27 OCPP16RegistrationStatus
,
28 OCPP16StatusNotificationResponse
,
29 } from
'../../../types/ocpp/1.6/Responses';
31 OCPP16AuthorizationStatus
,
32 OCPP16AuthorizeRequest
,
33 OCPP16AuthorizeResponse
,
34 OCPP16StartTransactionRequest
,
35 OCPP16StartTransactionResponse
,
36 OCPP16StopTransactionRequest
,
37 OCPP16StopTransactionResponse
,
38 } from
'../../../types/ocpp/1.6/Transaction';
39 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
40 import type { ResponseHandler
} from
'../../../types/ocpp/Responses';
41 import logger from
'../../../utils/Logger';
42 import Utils from
'../../../utils/Utils';
43 import type ChargingStation from
'../../ChargingStation';
44 import { ChargingStationConfigurationUtils
} from
'../../ChargingStationConfigurationUtils';
45 import { ChargingStationUtils
} from
'../../ChargingStationUtils';
46 import OCPPResponseService from
'../OCPPResponseService';
47 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
49 const moduleName
= 'OCPP16ResponseService';
51 export default class OCPP16ResponseService
extends OCPPResponseService
{
52 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
53 private jsonSchemas
: Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>;
55 public constructor() {
56 if (new.target
?.name
=== moduleName
) {
57 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
60 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
61 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
62 [OCPP16RequestCommand
.HEARTBEAT
, this.emptyResponseHandler
.bind(this)],
63 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
64 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
65 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
66 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
67 [OCPP16RequestCommand
.METER_VALUES
, this.emptyResponseHandler
.bind(this)],
68 [OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
70 this.jsonSchemas
= new Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>([
72 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
76 path
.dirname(fileURLToPath(import.meta
.url
)),
77 '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
81 ) as JSONSchemaType
<OCPP16BootNotificationResponse
>,
84 OCPP16RequestCommand
.HEARTBEAT
,
88 path
.dirname(fileURLToPath(import.meta
.url
)),
89 '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
93 ) as JSONSchemaType
<OCPP16HeartbeatResponse
>,
96 OCPP16RequestCommand
.AUTHORIZE
,
100 path
.dirname(fileURLToPath(import.meta
.url
)),
101 '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
105 ) as JSONSchemaType
<OCPP16AuthorizeResponse
>,
108 OCPP16RequestCommand
.START_TRANSACTION
,
112 path
.dirname(fileURLToPath(import.meta
.url
)),
113 '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
117 ) as JSONSchemaType
<OCPP16StartTransactionResponse
>,
120 OCPP16RequestCommand
.STOP_TRANSACTION
,
124 path
.dirname(fileURLToPath(import.meta
.url
)),
125 '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
129 ) as JSONSchemaType
<OCPP16StopTransactionResponse
>,
132 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
136 path
.dirname(fileURLToPath(import.meta
.url
)),
137 '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
141 ) as JSONSchemaType
<OCPP16StatusNotificationResponse
>,
144 OCPP16RequestCommand
.METER_VALUES
,
148 path
.dirname(fileURLToPath(import.meta
.url
)),
149 '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
153 ) as JSONSchemaType
<OCPP16MeterValuesResponse
>,
156 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
160 path
.dirname(fileURLToPath(import.meta
.url
)),
161 '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json'
165 ) as JSONSchemaType
<DiagnosticsStatusNotificationResponse
>,
168 this.validatePayload
.bind(this);
171 public async responseHandler(
172 chargingStation
: ChargingStation
,
173 commandName
: OCPP16RequestCommand
,
175 requestPayload
: JsonType
177 if (chargingStation
.isRegistered() || commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
) {
179 this.responseHandlers
.has(commandName
) &&
180 ChargingStationUtils
.isRequestCommandSupported(commandName
, chargingStation
)
183 this.validatePayload(chargingStation
, commandName
, payload
);
184 await this.responseHandlers
.get(commandName
)(chargingStation
, payload
, requestPayload
);
187 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
195 ErrorType
.NOT_IMPLEMENTED
,
196 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
207 ErrorType
.SECURITY_ERROR
,
208 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
212 )} while the charging station is not registered on the central server. `,
219 private validatePayload(
220 chargingStation
: ChargingStation
,
221 commandName
: OCPP16RequestCommand
,
224 if (this.jsonSchemas
.has(commandName
)) {
225 return this.validateResponsePayload(
228 this.jsonSchemas
.get(commandName
),
233 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
238 private handleResponseBootNotification(
239 chargingStation
: ChargingStation
,
240 payload
: OCPP16BootNotificationResponse
242 if (payload
.status === OCPP16RegistrationStatus
.ACCEPTED
) {
243 ChargingStationConfigurationUtils
.addConfigurationKey(
245 OCPP16StandardParametersKey
.HeartbeatInterval
,
246 payload
.interval
.toString(),
248 { overwrite
: true, save
: true }
250 ChargingStationConfigurationUtils
.addConfigurationKey(
252 OCPP16StandardParametersKey
.HeartBeatInterval
,
253 payload
.interval
.toString(),
255 { overwrite
: true, save
: true }
257 chargingStation
.heartbeatSetInterval
258 ? chargingStation
.restartHeartbeat()
259 : chargingStation
.startHeartbeat();
261 if (Object.values(OCPP16RegistrationStatus
).includes(payload
.status)) {
262 const logMsg
= `${chargingStation.logPrefix()} Charging station in '${
264 }' state on the central server`;
265 payload
.status === OCPP16RegistrationStatus
.REJECTED
266 ? logger
.warn(logMsg
)
267 : logger
.info(logMsg
);
270 chargingStation
.logPrefix() +
271 ' Charging station boot notification response received: %j with undefined registration status',
277 private handleResponseAuthorize(
278 chargingStation
: ChargingStation
,
279 payload
: OCPP16AuthorizeResponse
,
280 requestPayload
: OCPP16AuthorizeRequest
282 let authorizeConnectorId
: number;
283 for (const connectorId
of chargingStation
.connectors
.keys()) {
286 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
=== requestPayload
.idTag
288 authorizeConnectorId
= connectorId
;
292 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
293 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true;
295 `${chargingStation.logPrefix()} IdTag '${
297 }' authorized on connector ${authorizeConnectorId}`
300 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
301 delete chargingStation
.getConnectorStatus(authorizeConnectorId
).authorizeIdTag
;
303 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' refused with status '${
304 payload.idTagInfo.status
305 }' on connector ${authorizeConnectorId}`
310 private async handleResponseStartTransaction(
311 chargingStation
: ChargingStation
,
312 payload
: OCPP16StartTransactionResponse
,
313 requestPayload
: OCPP16StartTransactionRequest
315 const connectorId
= requestPayload
.connectorId
;
317 let transactionConnectorId
: number;
318 for (const id
of chargingStation
.connectors
.keys()) {
319 if (id
> 0 && id
=== connectorId
) {
320 transactionConnectorId
= id
;
324 if (!transactionConnectorId
) {
326 chargingStation
.logPrefix() +
327 ' Trying to start a transaction on a non existing connector Id ' +
328 connectorId
.toString()
333 chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
=== true &&
334 chargingStation
.getAuthorizeRemoteTxRequests() &&
335 chargingStation
.getLocalAuthListEnabled() &&
336 chargingStation
.hasAuthorizedTags() &&
337 chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
=== false
340 chargingStation
.logPrefix() +
341 ' Trying to start a transaction with a not local authorized idTag ' +
342 chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+
343 ' on connector Id ' +
344 connectorId
.toString()
346 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
350 chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
=== true &&
351 chargingStation
.getAuthorizeRemoteTxRequests() &&
352 chargingStation
.getMustAuthorizeAtRemoteStart() &&
353 chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
=== false &&
354 chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
=== false
357 chargingStation
.logPrefix() +
358 ' Trying to start a transaction with a not authorized idTag ' +
359 chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+
360 ' on connector Id ' +
361 connectorId
.toString()
363 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
367 chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
&&
368 chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
!== requestPayload
.idTag
371 chargingStation
.logPrefix() +
372 ' Trying to start a transaction with an idTag ' +
373 requestPayload
.idTag
+
374 ' different from the authorize request one ' +
375 chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+
376 ' on connector Id ' +
377 connectorId
.toString()
379 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
383 chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
&&
384 chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
!== requestPayload
.idTag
387 chargingStation
.logPrefix() +
388 ' Trying to start a transaction with an idTag ' +
389 requestPayload
.idTag
+
390 ' different from the local authorized one ' +
391 chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+
392 ' on connector Id ' +
393 connectorId
.toString()
395 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
398 if (chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
=== true) {
400 chargingStation
.logPrefix() +
401 ' Trying to start a transaction on an already used connector ' +
402 connectorId
.toString() +
404 chargingStation
.getConnectorStatus(connectorId
)
409 chargingStation
.getConnectorStatus(connectorId
)?.status !==
410 OCPP16ChargePointStatus
.AVAILABLE
&&
411 chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.PREPARING
414 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
415 chargingStation.getConnectorStatus(connectorId)?.status
420 // if (!Number.isInteger(payload.transactionId)) {
422 // `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
423 // payload.transactionId
424 // }, converting to integer`
426 // payload.transactionId = Utils.convertToInt(payload.transactionId);
429 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
430 chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
431 chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
432 chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
433 chargingStation
.getConnectorStatus(
435 ).transactionEnergyActiveImportRegisterValue
= 0;
436 chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
=
437 OCPP16ServiceUtils
.buildTransactionBeginMeterValue(
440 requestPayload
.meterStart
442 chargingStation
.getBeginEndMeterValues() &&
443 (await chargingStation
.ocppRequestService
.requestHandler
<
444 OCPP16MeterValuesRequest
,
445 OCPP16MeterValuesResponse
446 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
448 transactionId
: payload
.transactionId
,
449 meterValue
: [chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
],
451 await chargingStation
.ocppRequestService
.requestHandler
<
452 OCPP16StatusNotificationRequest
,
453 OCPP16StatusNotificationResponse
454 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
456 status: OCPP16ChargePointStatus
.CHARGING
,
457 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
459 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.CHARGING
;
461 chargingStation
.logPrefix() +
463 payload
.transactionId
.toString() +
465 chargingStation
.stationInfo
.chargingStationId
+
467 connectorId
.toString() +
469 requestPayload
.idTag
+
472 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
473 chargingStation
.powerDivider
++;
475 const configuredMeterValueSampleInterval
=
476 ChargingStationConfigurationUtils
.getConfigurationKey(
478 OCPP16StandardParametersKey
.MeterValueSampleInterval
480 chargingStation
.startMeterValues(
482 configuredMeterValueSampleInterval
483 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
488 chargingStation
.logPrefix() +
489 ' Starting transaction id ' +
490 payload
.transactionId
.toString() +
491 " REJECTED with status '" +
492 payload
?.idTagInfo
?.status +
494 requestPayload
.idTag
+
497 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
501 private async resetConnectorOnStartTransactionError(
502 chargingStation
: ChargingStation
,
505 chargingStation
.resetConnectorStatus(connectorId
);
507 chargingStation
.getConnectorStatus(connectorId
).status !== OCPP16ChargePointStatus
.AVAILABLE
509 await chargingStation
.ocppRequestService
.requestHandler
<
510 OCPP16StatusNotificationRequest
,
511 OCPP16StatusNotificationResponse
512 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
514 status: OCPP16ChargePointStatus
.AVAILABLE
,
515 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
517 chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
521 private async handleResponseStopTransaction(
522 chargingStation
: ChargingStation
,
523 payload
: OCPP16StopTransactionResponse
,
524 requestPayload
: OCPP16StopTransactionRequest
526 const transactionConnectorId
= chargingStation
.getConnectorIdByTransactionId(
527 requestPayload
.transactionId
529 if (!transactionConnectorId
) {
531 chargingStation
.logPrefix() +
532 ' Trying to stop a non existing transaction ' +
533 requestPayload
.transactionId
.toString()
537 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
538 chargingStation
.getBeginEndMeterValues() &&
539 !chargingStation
.getOcppStrictCompliance() &&
540 chargingStation
.getOutOfOrderEndMeterValues() &&
541 (await chargingStation
.ocppRequestService
.requestHandler
<
542 OCPP16MeterValuesRequest
,
543 OCPP16MeterValuesResponse
544 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
545 connectorId
: transactionConnectorId
,
546 transactionId
: requestPayload
.transactionId
,
548 OCPP16ServiceUtils
.buildTransactionEndMeterValue(
550 transactionConnectorId
,
551 requestPayload
.meterStop
556 !chargingStation
.isChargingStationAvailable() ||
557 !chargingStation
.isConnectorAvailable(transactionConnectorId
)
559 await chargingStation
.ocppRequestService
.requestHandler
<
560 OCPP16StatusNotificationRequest
,
561 OCPP16StatusNotificationResponse
562 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
563 connectorId
: transactionConnectorId
,
564 status: OCPP16ChargePointStatus
.UNAVAILABLE
,
565 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
567 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
568 OCPP16ChargePointStatus
.UNAVAILABLE
;
570 await chargingStation
.ocppRequestService
.requestHandler
<
571 OCPP16BootNotificationRequest
,
572 OCPP16BootNotificationResponse
573 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
574 connectorId
: transactionConnectorId
,
575 status: OCPP16ChargePointStatus
.AVAILABLE
,
576 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
578 chargingStation
.getConnectorStatus(transactionConnectorId
).status =
579 OCPP16ChargePointStatus
.AVAILABLE
;
581 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
582 chargingStation
.powerDivider
--;
584 chargingStation
.resetConnectorStatus(transactionConnectorId
);
586 chargingStation
.logPrefix() +
588 requestPayload
.transactionId
.toString() +
590 chargingStation
.stationInfo
.chargingStationId
+
592 transactionConnectorId
.toString()
596 chargingStation
.logPrefix() +
597 ' Stopping transaction id ' +
598 requestPayload
.transactionId
.toString() +
599 " REJECTED with status '" +
600 payload
.idTagInfo
?.status +