1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import { AuthorizeRequest
, OCPP16AuthorizationStatus
, OCPP16AuthorizeResponse
, OCPP16StartTransactionResponse
, OCPP16StopTransactionResponse
, StartTransactionRequest
, StopTransactionRequest
} from
'../../../types/ocpp/1.6/Transaction';
4 import { HeartbeatRequest
, OCPP16RequestCommand
, StatusNotificationRequest
} from
'../../../types/ocpp/1.6/Requests';
5 import { HeartbeatResponse
, OCPP16BootNotificationResponse
, OCPP16RegistrationStatus
, StatusNotificationResponse
} from
'../../../types/ocpp/1.6/Responses';
6 import { MeterValuesRequest
, MeterValuesResponse
} from
'../../../types/ocpp/1.6/MeterValues';
8 import type ChargingStation from
'../../ChargingStation';
9 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
10 import { JsonType
} from
'../../../types/JsonType';
11 import { OCPP16ChargePointStatus
} from
'../../../types/ocpp/1.6/ChargePointStatus';
12 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
13 import { OCPP16StandardParametersKey
} from
'../../../types/ocpp/1.6/Configuration';
14 import OCPPError from
'../../../exception/OCPPError';
15 import OCPPResponseService from
'../OCPPResponseService';
16 import { ResponseHandler
} from
'../../../types/ocpp/Responses';
17 import Utils from
'../../../utils/Utils';
18 import logger from
'../../../utils/Logger';
20 const moduleName
= 'OCPP16ResponseService';
22 export default class OCPP16ResponseService
extends OCPPResponseService
{
23 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
25 public constructor(chargingStation
: ChargingStation
) {
26 if (new.target
?.name
=== moduleName
) {
27 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
29 super(chargingStation
);
30 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
31 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
32 [OCPP16RequestCommand
.HEARTBEAT
, this.handleResponseHeartbeat
.bind(this)],
33 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
34 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
35 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
36 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.handleResponseStatusNotification
.bind(this)],
37 [OCPP16RequestCommand
.METER_VALUES
, this.handleResponseMeterValues
.bind(this)]
41 public async handleResponse(commandName
: OCPP16RequestCommand
, payload
: JsonType
| string, requestPayload
: JsonType
): Promise
<void> {
42 if (this.chargingStation
.isRegistered() || commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
) {
43 if (this.responseHandlers
.has(commandName
)) {
45 await this.responseHandlers
.get(commandName
)(payload
, requestPayload
);
47 logger
.error(this.chargingStation
.logPrefix() + ' Handle request response error: %j', error
);
52 throw new OCPPError(ErrorType
.NOT_IMPLEMENTED
, `${commandName} is not implemented to handle request response payload ${JSON.stringify(payload, null, 2)}`, commandName
);
55 throw new OCPPError(ErrorType
.SECURITY_ERROR
, `${commandName} cannot be issued to handle request response payload ${JSON.stringify(payload, null, 2)} while the charging station is not registered on the central server. `, commandName
);
59 private handleResponseBootNotification(payload
: OCPP16BootNotificationResponse
): void {
60 if (payload
.status === OCPP16RegistrationStatus
.ACCEPTED
) {
61 this.chargingStation
.addConfigurationKey(OCPP16StandardParametersKey
.HeartBeatInterval
, payload
.interval
.toString());
62 this.chargingStation
.addConfigurationKey(OCPP16StandardParametersKey
.HeartbeatInterval
, payload
.interval
.toString(), { visible
: false });
63 this.chargingStation
.heartbeatSetInterval
? this.chargingStation
.restartHeartbeat() : this.chargingStation
.startHeartbeat();
65 if (Object.values(OCPP16RegistrationStatus
).includes(payload
.status)) {
66 const logMsg
= `${this.chargingStation.logPrefix()} Charging station in '${payload.status}' state on the central server`;
67 payload
.status === OCPP16RegistrationStatus
.REJECTED
? logger
.warn(logMsg
) : logger
.info(logMsg
);
69 logger
.error(this.chargingStation
.logPrefix() + ' Charging station boot notification response received: %j with undefined registration status', payload
);
73 private handleResponseHeartbeat(payload
: HeartbeatResponse
, requestPayload
: HeartbeatRequest
): void {
74 logger
.debug(this.chargingStation
.logPrefix() + ' Heartbeat response received: %j to Heartbeat request: %j', payload
, requestPayload
);
77 private handleResponseAuthorize(payload
: OCPP16AuthorizeResponse
, requestPayload
: AuthorizeRequest
): void {
78 let authorizeConnectorId
: number;
79 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
80 if (connectorId
> 0 && this.chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
=== requestPayload
.idTag
) {
81 authorizeConnectorId
= connectorId
;
85 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
86 this.chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true;
87 logger
.debug(`${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} authorized on connector ${authorizeConnectorId}`);
89 this.chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
90 delete this.chargingStation
.getConnectorStatus(authorizeConnectorId
).authorizeIdTag
;
91 logger
.debug(`${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status ${payload.idTagInfo.status} on connector ${authorizeConnectorId}`);
95 private async handleResponseStartTransaction(payload
: OCPP16StartTransactionResponse
, requestPayload
: StartTransactionRequest
): Promise
<void> {
96 const connectorId
= requestPayload
.connectorId
;
98 let transactionConnectorId
: number;
99 for (const id
of this.chargingStation
.connectors
.keys()) {
100 if (id
> 0 && id
=== connectorId
) {
101 transactionConnectorId
= id
;
105 if (!transactionConnectorId
) {
106 logger
.error(this.chargingStation
.logPrefix() + ' Trying to start a transaction on a non existing connector Id ' + connectorId
.toString());
109 if (this.chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
&& this.chargingStation
.getAuthorizeRemoteTxRequests()
110 && this.chargingStation
.getLocalAuthListEnabled() && this.chargingStation
.hasAuthorizedTags() && !this.chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
) {
111 logger
.error(this.chargingStation
.logPrefix() + ' Trying to start a transaction with a not local authorized idTag ' + this.chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+ ' on connector Id ' + connectorId
.toString());
112 await this.resetConnectorOnStartTransactionError(connectorId
);
115 if (this.chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
&& this.chargingStation
.getAuthorizeRemoteTxRequests()
116 && this.chargingStation
.getMayAuthorizeAtRemoteStart() && !this.chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
117 && !this.chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
) {
118 logger
.error(this.chargingStation
.logPrefix() + ' Trying to start a transaction with a not authorized idTag ' + this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+ ' on connector Id ' + connectorId
.toString());
119 await this.resetConnectorOnStartTransactionError(connectorId
);
122 if (this.chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
&& this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
!== requestPayload
.idTag
) {
123 logger
.error(this.chargingStation
.logPrefix() + ' Trying to start a transaction with an idTag ' + requestPayload
.idTag
+ ' different from the authorize request one ' + this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+ ' on connector Id ' + connectorId
.toString());
124 await this.resetConnectorOnStartTransactionError(connectorId
);
127 if (this.chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
128 && this.chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
!== requestPayload
.idTag
) {
129 logger
.error(this.chargingStation
.logPrefix() + ' Trying to start a transaction with an idTag ' + requestPayload
.idTag
+ ' different from the local authorized one ' + this.chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+ ' on connector Id ' + connectorId
.toString());
130 await this.resetConnectorOnStartTransactionError(connectorId
);
133 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
134 logger
.debug(this.chargingStation
.logPrefix() + ' Trying to start a transaction on an already used connector ' + connectorId
.toString() + ': %j', this.chargingStation
.getConnectorStatus(connectorId
));
137 if (this.chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.AVAILABLE
138 && this.chargingStation
.getConnectorStatus(connectorId
)?.status !== OCPP16ChargePointStatus
.PREPARING
) {
139 logger
.error(`${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${this.chargingStation.getConnectorStatus(connectorId)?.status}`);
142 if (!Number.isInteger(payload
.transactionId
)) {
143 logger
.warn(`${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${payload.transactionId}, converting to integer`);
144 payload
.transactionId
= Utils
.convertToInt(payload
.transactionId
);
147 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
148 this.chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
149 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
150 this.chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
151 this.chargingStation
.getConnectorStatus(connectorId
).transactionEnergyActiveImportRegisterValue
= 0;
152 this.chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
= OCPP16ServiceUtils
.buildTransactionBeginMeterValue(this.chargingStation
, connectorId
,
153 requestPayload
.meterStart
);
154 this.chargingStation
.getBeginEndMeterValues() && await this.chargingStation
.ocppRequestService
.sendTransactionBeginMeterValues(connectorId
, payload
.transactionId
,
155 this.chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
);
156 await this.chargingStation
.ocppRequestService
.sendStatusNotification(connectorId
, OCPP16ChargePointStatus
.CHARGING
);
157 this.chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.CHARGING
;
158 logger
.info(this.chargingStation
.logPrefix() + ' Transaction ' + payload
.transactionId
.toString() + ' STARTED on ' + this.chargingStation
.stationInfo
.chargingStationId
+ '#' + connectorId
.toString() + ' for idTag ' + requestPayload
.idTag
);
159 if (this.chargingStation
.stationInfo
.powerSharedByConnectors
) {
160 this.chargingStation
.stationInfo
.powerDivider
++;
162 const configuredMeterValueSampleInterval
= this.chargingStation
.getConfigurationKey(OCPP16StandardParametersKey
.MeterValueSampleInterval
);
163 this.chargingStation
.startMeterValues(connectorId
, configuredMeterValueSampleInterval
? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000 : 60000);
165 logger
.warn(this.chargingStation
.logPrefix() + ' Starting transaction id ' + payload
.transactionId
.toString() + ' REJECTED with status ' + payload
?.idTagInfo
?.status + ', idTag ' + requestPayload
.idTag
);
166 await this.resetConnectorOnStartTransactionError(connectorId
);
170 private async resetConnectorOnStartTransactionError(connectorId
: number): Promise
<void> {
171 this.chargingStation
.resetConnectorStatus(connectorId
);
172 if (this.chargingStation
.getConnectorStatus(connectorId
).status !== OCPP16ChargePointStatus
.AVAILABLE
) {
173 await this.chargingStation
.ocppRequestService
.sendStatusNotification(connectorId
, OCPP16ChargePointStatus
.AVAILABLE
);
174 this.chargingStation
.getConnectorStatus(connectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
178 private async handleResponseStopTransaction(payload
: OCPP16StopTransactionResponse
, requestPayload
: StopTransactionRequest
): Promise
<void> {
179 let transactionConnectorId
: number;
180 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
181 if (connectorId
> 0 && this.chargingStation
.getConnectorStatus(connectorId
)?.transactionId
=== requestPayload
.transactionId
) {
182 transactionConnectorId
= connectorId
;
186 if (!transactionConnectorId
) {
187 logger
.error(this.chargingStation
.logPrefix() + ' Trying to stop a non existing transaction ' + requestPayload
.transactionId
.toString());
190 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
191 (this.chargingStation
.getBeginEndMeterValues() && !this.chargingStation
.getOcppStrictCompliance() && this.chargingStation
.getOutOfOrderEndMeterValues())
192 && await this.chargingStation
.ocppRequestService
.sendTransactionEndMeterValues(transactionConnectorId
, requestPayload
.transactionId
,
193 OCPP16ServiceUtils
.buildTransactionEndMeterValue(this.chargingStation
, transactionConnectorId
, requestPayload
.meterStop
));
194 if (!this.chargingStation
.isChargingStationAvailable() || !this.chargingStation
.isConnectorAvailable(transactionConnectorId
)) {
195 await this.chargingStation
.ocppRequestService
.sendStatusNotification(transactionConnectorId
, OCPP16ChargePointStatus
.UNAVAILABLE
);
196 this.chargingStation
.getConnectorStatus(transactionConnectorId
).status = OCPP16ChargePointStatus
.UNAVAILABLE
;
198 await this.chargingStation
.ocppRequestService
.sendStatusNotification(transactionConnectorId
, OCPP16ChargePointStatus
.AVAILABLE
);
199 this.chargingStation
.getConnectorStatus(transactionConnectorId
).status = OCPP16ChargePointStatus
.AVAILABLE
;
201 if (this.chargingStation
.stationInfo
.powerSharedByConnectors
) {
202 this.chargingStation
.stationInfo
.powerDivider
--;
204 logger
.info(this.chargingStation
.logPrefix() + ' Transaction ' + requestPayload
.transactionId
.toString() + ' STOPPED on ' + this.chargingStation
.stationInfo
.chargingStationId
+ '#' + transactionConnectorId
.toString());
205 this.chargingStation
.resetConnectorStatus(transactionConnectorId
);
207 logger
.warn(this.chargingStation
.logPrefix() + ' Stopping transaction id ' + requestPayload
.transactionId
.toString() + ' REJECTED with status ' + payload
.idTagInfo
?.status);
211 private handleResponseStatusNotification(payload
: StatusNotificationRequest
, requestPayload
: StatusNotificationResponse
): void {
212 logger
.debug(this.chargingStation
.logPrefix() + ' Status notification response received: %j to StatusNotification request: %j', payload
, requestPayload
);
215 private handleResponseMeterValues(payload
: MeterValuesRequest
, requestPayload
: MeterValuesResponse
): void {
216 logger
.debug(this.chargingStation
.logPrefix() + ' MeterValues response received: %j to MeterValues request: %j', payload
, requestPayload
);