1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import { type ChargingStation
, ChargingStationConfigurationUtils
} from
'../../../charging-station';
6 import { OCPPError
} from
'../../../exception';
8 type ChangeAvailabilityResponse
,
9 type ChangeConfigurationResponse
,
10 type ClearChargingProfileResponse
,
13 type GetConfigurationResponse
,
14 type GetDiagnosticsResponse
,
17 OCPP16AuthorizationStatus
,
18 type OCPP16AuthorizeRequest
,
19 type OCPP16AuthorizeResponse
,
20 type OCPP16BootNotificationResponse
,
21 OCPP16ChargePointStatus
,
22 type OCPP16DataTransferResponse
,
23 type OCPP16DiagnosticsStatusNotificationResponse
,
24 type OCPP16FirmwareStatusNotificationResponse
,
25 type OCPP16GetCompositeScheduleResponse
,
26 type OCPP16HeartbeatResponse
,
27 OCPP16IncomingRequestCommand
,
28 type OCPP16MeterValuesRequest
,
29 type OCPP16MeterValuesResponse
,
31 OCPP16StandardParametersKey
,
32 type OCPP16StartTransactionRequest
,
33 type OCPP16StartTransactionResponse
,
34 type OCPP16StatusNotificationResponse
,
35 type OCPP16StopTransactionRequest
,
36 type OCPP16StopTransactionResponse
,
37 type OCPP16TriggerMessageResponse
,
38 type OCPP16UpdateFirmwareResponse
,
40 RegistrationStatusEnumType
,
42 type SetChargingProfileResponse
,
43 type UnlockConnectorResponse
,
44 } from
'../../../types';
45 import { Constants
, Utils
, logger
} from
'../../../utils';
46 import { OCPP16ServiceUtils
, OCPPResponseService
} from
'../internal';
48 const moduleName
= 'OCPP16ResponseService';
50 export class OCPP16ResponseService
extends OCPPResponseService
{
51 public jsonIncomingRequestResponseSchemas
: Map
<
52 OCPP16IncomingRequestCommand
,
53 JSONSchemaType
<JsonObject
>
56 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
57 private jsonSchemas
: Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>;
59 public constructor() {
60 // if (new.target?.name === moduleName) {
61 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
63 super(OCPPVersion
.VERSION_16
);
64 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
65 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
66 [OCPP16RequestCommand
.HEARTBEAT
, this.emptyResponseHandler
.bind(this)],
67 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
68 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
69 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
70 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
71 [OCPP16RequestCommand
.METER_VALUES
, this.emptyResponseHandler
.bind(this)],
72 [OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
73 [OCPP16RequestCommand
.DATA_TRANSFER
, this.emptyResponseHandler
.bind(this)],
74 [OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
, this.emptyResponseHandler
.bind(this)],
76 this.jsonSchemas
= new Map
<OCPP16RequestCommand
, JSONSchemaType
<JsonObject
>>([
78 OCPP16RequestCommand
.BOOT_NOTIFICATION
,
79 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16BootNotificationResponse
>(
80 '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
86 OCPP16RequestCommand
.HEARTBEAT
,
87 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16HeartbeatResponse
>(
88 '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
94 OCPP16RequestCommand
.AUTHORIZE
,
95 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16AuthorizeResponse
>(
96 '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
102 OCPP16RequestCommand
.START_TRANSACTION
,
103 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16StartTransactionResponse
>(
104 '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
110 OCPP16RequestCommand
.STOP_TRANSACTION
,
111 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16StopTransactionResponse
>(
112 '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
118 OCPP16RequestCommand
.STATUS_NOTIFICATION
,
119 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16StatusNotificationResponse
>(
120 '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
126 OCPP16RequestCommand
.METER_VALUES
,
127 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16MeterValuesResponse
>(
128 '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
134 OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
135 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16DiagnosticsStatusNotificationResponse
>(
136 '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
142 OCPP16RequestCommand
.DATA_TRANSFER
,
143 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16DataTransferResponse
>(
144 '../../../assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
150 OCPP16RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
151 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16FirmwareStatusNotificationResponse
>(
152 '../../../assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
158 this.jsonIncomingRequestResponseSchemas
= new Map([
160 OCPP16IncomingRequestCommand
.RESET
,
161 OCPP16ServiceUtils
.parseJsonSchemaFile
<GenericResponse
>(
162 '../../../assets/json-schemas/ocpp/1.6/ResetResponse.json',
168 OCPP16IncomingRequestCommand
.CLEAR_CACHE
,
169 OCPP16ServiceUtils
.parseJsonSchemaFile
<GenericResponse
>(
170 '../../../assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
176 OCPP16IncomingRequestCommand
.CHANGE_AVAILABILITY
,
177 OCPP16ServiceUtils
.parseJsonSchemaFile
<ChangeAvailabilityResponse
>(
178 '../../../assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
184 OCPP16IncomingRequestCommand
.UNLOCK_CONNECTOR
,
185 OCPP16ServiceUtils
.parseJsonSchemaFile
<UnlockConnectorResponse
>(
186 '../../../assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
192 OCPP16IncomingRequestCommand
.GET_CONFIGURATION
,
193 OCPP16ServiceUtils
.parseJsonSchemaFile
<GetConfigurationResponse
>(
194 '../../../assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
200 OCPP16IncomingRequestCommand
.CHANGE_CONFIGURATION
,
201 OCPP16ServiceUtils
.parseJsonSchemaFile
<ChangeConfigurationResponse
>(
202 '../../../assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
208 OCPP16IncomingRequestCommand
.GET_COMPOSITE_SCHEDULE
,
209 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16GetCompositeScheduleResponse
>(
210 '../../../assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
216 OCPP16IncomingRequestCommand
.SET_CHARGING_PROFILE
,
217 OCPP16ServiceUtils
.parseJsonSchemaFile
<SetChargingProfileResponse
>(
218 '../../../assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
224 OCPP16IncomingRequestCommand
.CLEAR_CHARGING_PROFILE
,
225 OCPP16ServiceUtils
.parseJsonSchemaFile
<ClearChargingProfileResponse
>(
226 '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
232 OCPP16IncomingRequestCommand
.REMOTE_START_TRANSACTION
,
233 OCPP16ServiceUtils
.parseJsonSchemaFile
<GenericResponse
>(
234 '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
240 OCPP16IncomingRequestCommand
.REMOTE_STOP_TRANSACTION
,
241 OCPP16ServiceUtils
.parseJsonSchemaFile
<GenericResponse
>(
242 '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
248 OCPP16IncomingRequestCommand
.GET_DIAGNOSTICS
,
249 OCPP16ServiceUtils
.parseJsonSchemaFile
<GetDiagnosticsResponse
>(
250 '../../../assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
256 OCPP16IncomingRequestCommand
.TRIGGER_MESSAGE
,
257 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16TriggerMessageResponse
>(
258 '../../../assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
264 OCPP16IncomingRequestCommand
.DATA_TRANSFER
,
265 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16DataTransferResponse
>(
266 '../../../assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
272 OCPP16IncomingRequestCommand
.UPDATE_FIRMWARE
,
273 OCPP16ServiceUtils
.parseJsonSchemaFile
<OCPP16UpdateFirmwareResponse
>(
274 '../../../assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
280 this.validatePayload
= this.validatePayload
.bind(this) as (
281 chargingStation
: ChargingStation
,
282 commandName
: OCPP16RequestCommand
,
287 public async responseHandler(
288 chargingStation
: ChargingStation
,
289 commandName
: OCPP16RequestCommand
,
291 requestPayload
: JsonType
294 chargingStation
.isRegistered() === true ||
295 commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
298 this.responseHandlers
.has(commandName
) === true &&
299 OCPP16ServiceUtils
.isRequestCommandSupported(chargingStation
, commandName
) === true
302 this.validatePayload(chargingStation
, commandName
, payload
);
303 await this.responseHandlers
.get(commandName
)(chargingStation
, payload
, requestPayload
);
306 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
314 ErrorType
.NOT_IMPLEMENTED
,
315 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
326 ErrorType
.SECURITY_ERROR
,
327 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
331 )} while the charging station is not registered on the central server.`,
338 private validatePayload(
339 chargingStation
: ChargingStation
,
340 commandName
: OCPP16RequestCommand
,
343 if (this.jsonSchemas
.has(commandName
) === true) {
344 return this.validateResponsePayload(
347 this.jsonSchemas
.get(commandName
),
352 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
357 private handleResponseBootNotification(
358 chargingStation
: ChargingStation
,
359 payload
: OCPP16BootNotificationResponse
361 if (payload
.status === RegistrationStatusEnumType
.ACCEPTED
) {
362 ChargingStationConfigurationUtils
.addConfigurationKey(
364 OCPP16StandardParametersKey
.HeartbeatInterval
,
365 payload
.interval
.toString(),
367 { overwrite
: true, save
: true }
369 ChargingStationConfigurationUtils
.addConfigurationKey(
371 OCPP16StandardParametersKey
.HeartBeatInterval
,
372 payload
.interval
.toString(),
374 { overwrite
: true, save
: true }
376 OCPP16ServiceUtils
.startHeartbeatInterval(chargingStation
, payload
.interval
);
378 if (Object.values(RegistrationStatusEnumType
).includes(payload
.status)) {
379 const logMsg
= `${chargingStation.logPrefix()} Charging station in '${
381 }' state on the central server`;
382 payload
.status === RegistrationStatusEnumType
.REJECTED
383 ? logger
.warn(logMsg
)
384 : logger
.info(logMsg
);
387 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
393 private handleResponseAuthorize(
394 chargingStation
: ChargingStation
,
395 payload
: OCPP16AuthorizeResponse
,
396 requestPayload
: OCPP16AuthorizeRequest
398 let authorizeConnectorId
: number;
399 for (const connectorId
of chargingStation
.connectors
.keys()) {
402 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
=== requestPayload
.idTag
404 authorizeConnectorId
= connectorId
;
408 const authorizeConnectorIdDefined
= !Utils
.isNullOrUndefined(authorizeConnectorId
);
409 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
410 authorizeConnectorIdDefined
&&
411 (chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true);
413 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' accepted${
414 authorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}
` : ''
418 if (authorizeConnectorIdDefined
) {
419 chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
420 delete chargingStation
.getConnectorStatus(authorizeConnectorId
)?.authorizeIdTag
;
423 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' rejected with status '${
424 payload.idTagInfo.status
425 }'${authorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''}`
430 private async handleResponseStartTransaction(
431 chargingStation: ChargingStation,
432 payload: OCPP16StartTransactionResponse,
433 requestPayload: OCPP16StartTransactionRequest
435 const connectorId = requestPayload.connectorId;
437 let transactionConnectorId: number;
438 for (const id of chargingStation.connectors.keys()) {
439 if (id > 0 && id === connectorId) {
440 transactionConnectorId = id;
444 if (Utils.isNullOrUndefined(transactionConnectorId)) {
446 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector Id ${connectorId.toString()}
`
451 chargingStation.getConnectorStatus(connectorId)?.transactionRemoteStarted === true &&
452 chargingStation.getAuthorizeRemoteTxRequests() === true &&
453 chargingStation.getLocalAuthListEnabled() === true &&
454 chargingStation.hasIdTags() &&
455 chargingStation.getConnectorStatus(connectorId)?.idTagLocalAuthorized === false
458 `${chargingStation.logPrefix()} Trying to start a transaction
with a not local authorized idTag $
{
459 chargingStation
.getConnectorStatus(connectorId
)?.localAuthorizeIdTag
460 } on connector Id ${connectorId.toString()}
`
462 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
466 chargingStation.getConnectorStatus(connectorId)?.transactionRemoteStarted === true &&
467 chargingStation.getAuthorizeRemoteTxRequests() === true &&
468 chargingStation.getMustAuthorizeAtRemoteStart() === true &&
469 chargingStation.getConnectorStatus(connectorId)?.idTagLocalAuthorized === false &&
470 chargingStation.getConnectorStatus(connectorId)?.idTagAuthorized === false
473 `${chargingStation.logPrefix()} Trying to start a transaction
with a not authorized idTag $
{
474 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
475 } on connector Id ${connectorId.toString()}
`
477 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
481 chargingStation.getConnectorStatus(connectorId)?.idTagAuthorized &&
482 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag !== requestPayload.idTag
485 `${chargingStation.logPrefix()} Trying to start a transaction
with an idTag $
{
487 } different from the authorize request one $
{
488 chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
489 } on connector Id ${connectorId.toString()}
`
491 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
495 chargingStation.getConnectorStatus(connectorId)?.idTagLocalAuthorized &&
496 chargingStation.getConnectorStatus(connectorId)?.localAuthorizeIdTag !== requestPayload.idTag
499 `${chargingStation.logPrefix()} Trying to start a transaction
with an idTag $
{
501 } different from the local authorized one $
{
502 chargingStation
.getConnectorStatus(connectorId
)?.localAuthorizeIdTag
503 } on connector Id ${connectorId.toString()}
`
505 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
508 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
510 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector ${connectorId.toString()}
: %j
`,
511 chargingStation.getConnectorStatus(connectorId)
516 chargingStation.getConnectorStatus(connectorId)?.status !==
517 OCPP16ChargePointStatus.Available &&
518 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Preparing
521 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()}
with status $
{
522 chargingStation
.getConnectorStatus(connectorId
)?.status
527 // if (!Number.isInteger(payload.transactionId)) {
529 // `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()}
with a non integer transaction Id $
{
530 // payload.transactionId
531 // }, converting to integer`
533 // payload.transactionId = Utils.convertToInt(payload.transactionId);
536 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
537 chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
538 chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
539 chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
540 chargingStation
.getConnectorStatus(
542 ).transactionEnergyActiveImportRegisterValue
= 0;
543 chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
=
544 OCPP16ServiceUtils
.buildTransactionBeginMeterValue(
547 requestPayload
.meterStart
549 chargingStation
.getBeginEndMeterValues() &&
550 (await chargingStation
.ocppRequestService
.requestHandler
<
551 OCPP16MeterValuesRequest
,
552 OCPP16MeterValuesResponse
553 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
555 transactionId
: payload
.transactionId
,
556 meterValue
: [chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
],
558 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
561 OCPP16ChargePointStatus
.Charging
564 `${chargingStation.logPrefix()} Transaction ${payload.transactionId.toString()} STARTED on ${
565 chargingStation.stationInfo.chargingStationId
566 }#${connectorId.toString()} for idTag '${requestPayload.idTag}'`
568 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
569 chargingStation
.powerDivider
++;
571 const configuredMeterValueSampleInterval
=
572 ChargingStationConfigurationUtils
.getConfigurationKey(
574 OCPP16StandardParametersKey
.MeterValueSampleInterval
576 chargingStation
.startMeterValues(
578 configuredMeterValueSampleInterval
579 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
580 : Constants
.DEFAULT_METER_VALUES_INTERVAL
584 `${chargingStation.logPrefix()} Starting transaction id ${payload.transactionId.toString()} REJECTED with status '${
585 payload.idTagInfo?.status
586 }', idTag '${requestPayload.idTag}'`
588 await this.resetConnectorOnStartTransactionError(chargingStation
, connectorId
);
592 private async resetConnectorOnStartTransactionError(
593 chargingStation
: ChargingStation
,
596 chargingStation
.resetConnectorStatus(connectorId
);
598 chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.Available
600 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
603 OCPP16ChargePointStatus
.Available
608 private async handleResponseStopTransaction(
609 chargingStation
: ChargingStation
,
610 payload
: OCPP16StopTransactionResponse
,
611 requestPayload
: OCPP16StopTransactionRequest
613 const transactionConnectorId
= chargingStation
.getConnectorIdByTransactionId(
614 requestPayload
.transactionId
616 if (Utils
.isNullOrUndefined(transactionConnectorId
)) {
618 `${chargingStation.logPrefix()} Trying to stop a non existing transaction ${requestPayload.transactionId.toString()}`
622 chargingStation
.getBeginEndMeterValues() === true &&
623 chargingStation
.getOcppStrictCompliance() === false &&
624 chargingStation
.getOutOfOrderEndMeterValues() === true &&
625 (await chargingStation
.ocppRequestService
.requestHandler
<
626 OCPP16MeterValuesRequest
,
627 OCPP16MeterValuesResponse
628 >(chargingStation
, OCPP16RequestCommand
.METER_VALUES
, {
629 connectorId
: transactionConnectorId
,
630 transactionId
: requestPayload
.transactionId
,
632 OCPP16ServiceUtils
.buildTransactionEndMeterValue(
634 transactionConnectorId
,
635 requestPayload
.meterStop
640 chargingStation
.isChargingStationAvailable() === false ||
641 chargingStation
.isConnectorAvailable(transactionConnectorId
) === false
643 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
645 transactionConnectorId
,
646 OCPP16ChargePointStatus
.Unavailable
649 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
651 transactionConnectorId
,
652 OCPP16ChargePointStatus
.Available
655 if (chargingStation
.stationInfo
.powerSharedByConnectors
) {
656 chargingStation
.powerDivider
--;
658 chargingStation
.resetConnectorStatus(transactionConnectorId
);
659 const logMsg
= `${chargingStation.logPrefix()} Transaction ${requestPayload.transactionId.toString()} STOPPED on ${
660 chargingStation.stationInfo.chargingStationId
661 }#${transactionConnectorId?.toString()} with status '${
662 payload.idTagInfo?.status ?? 'undefined'
665 Utils
.isNullOrUndefined(payload
.idTagInfo
) ||
666 payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED