1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
4 OCPP16AuthorizationStatus
,
5 OCPP16AuthorizeRequest
,
6 OCPP16AuthorizeResponse
,
7 OCPP16StartTransactionRequest
,
8 OCPP16StartTransactionResponse
,
9 OCPP16StopTransactionRequest
,
10 OCPP16StopTransactionResponse
,
11 } from
'../../../types/ocpp/1.6/Transaction';
13 OCPP16BootNotificationRequest
,
14 OCPP16HeartbeatRequest
,
16 OCPP16StatusNotificationRequest
,
17 } from
'../../../types/ocpp/1.6/Requests';
19 OCPP16BootNotificationResponse
,
20 OCPP16HeartbeatResponse
,
21 OCPP16RegistrationStatus
,
22 OCPP16StatusNotificationResponse
,
23 } from
'../../../types/ocpp/1.6/Responses';
25 OCPP16MeterValuesRequest
,
26 OCPP16MeterValuesResponse
,
27 } from
'../../../types/ocpp/1.6/MeterValues';
29 import type ChargingStation from
'../../ChargingStation';
30 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
31 import { JsonType
} from
'../../../types/JsonType';
32 import { OCPP16ChargePointErrorCode
} from
'../../../types/ocpp/1.6/ChargePointErrorCode';
33 import { OCPP16ChargePointStatus
} from
'../../../types/ocpp/1.6/ChargePointStatus';
34 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
35 import { OCPP16StandardParametersKey
} from
'../../../types/ocpp/1.6/Configuration';
36 import OCPPError from
'../../../exception/OCPPError';
37 import OCPPResponseService from
'../OCPPResponseService';
38 import { ResponseHandler
} from
'../../../types/ocpp/Responses';
39 import Utils from
'../../../utils/Utils';
40 import logger from
'../../../utils/Logger';
42 const moduleName
= 'OCPP16ResponseService';
44 export default class OCPP16ResponseService
extends OCPPResponseService
{
45 private responseHandlers
: Map
<OCPP16RequestCommand
, ResponseHandler
>;
47 public constructor(chargingStation
: ChargingStation
) {
48 if (new.target
?.name
=== moduleName
) {
49 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
51 super(chargingStation
);
52 this.responseHandlers
= new Map
<OCPP16RequestCommand
, ResponseHandler
>([
53 [OCPP16RequestCommand
.BOOT_NOTIFICATION
, this.handleResponseBootNotification
.bind(this)],
54 [OCPP16RequestCommand
.HEARTBEAT
, this.handleResponseHeartbeat
.bind(this)],
55 [OCPP16RequestCommand
.AUTHORIZE
, this.handleResponseAuthorize
.bind(this)],
56 [OCPP16RequestCommand
.START_TRANSACTION
, this.handleResponseStartTransaction
.bind(this)],
57 [OCPP16RequestCommand
.STOP_TRANSACTION
, this.handleResponseStopTransaction
.bind(this)],
58 [OCPP16RequestCommand
.STATUS_NOTIFICATION
, this.handleResponseStatusNotification
.bind(this)],
59 [OCPP16RequestCommand
.METER_VALUES
, this.handleResponseMeterValues
.bind(this)],
63 public async handleResponse(
64 commandName
: OCPP16RequestCommand
,
65 payload
: JsonType
| string,
66 requestPayload
: JsonType
69 this.chargingStation
.isRegistered() ||
70 commandName
=== OCPP16RequestCommand
.BOOT_NOTIFICATION
72 if (this.responseHandlers
.has(commandName
)) {
74 await this.responseHandlers
.get(commandName
)(payload
, requestPayload
);
77 this.chargingStation
.logPrefix() + ' Handle request response error: %j',
85 ErrorType
.NOT_IMPLEMENTED
,
86 `${commandName} is not implemented to handle request response payload ${JSON.stringify(
96 ErrorType
.SECURITY_ERROR
,
97 `${commandName} cannot be issued to handle request response payload ${JSON.stringify(
101 )} while the charging station is not registered on the central server. `,
107 private handleResponseBootNotification(payload
: OCPP16BootNotificationResponse
): void {
108 if (payload
.status === OCPP16RegistrationStatus
.ACCEPTED
) {
109 this.chargingStation
.addConfigurationKey(
110 OCPP16StandardParametersKey
.HeartBeatInterval
,
111 payload
.interval
.toString(),
113 { overwrite
: true, save
: true }
115 this.chargingStation
.addConfigurationKey(
116 OCPP16StandardParametersKey
.HeartbeatInterval
,
117 payload
.interval
.toString(),
119 { overwrite
: true, save
: true }
121 this.chargingStation
.heartbeatSetInterval
122 ? this.chargingStation
.restartHeartbeat()
123 : this.chargingStation
.startHeartbeat();
125 if (Object.values(OCPP16RegistrationStatus
).includes(payload
.status)) {
126 const logMsg
= `${this.chargingStation.logPrefix()} Charging station in '${
128 }' state on the central server`;
129 payload
.status === OCPP16RegistrationStatus
.REJECTED
130 ? logger
.warn(logMsg
)
131 : logger
.info(logMsg
);
134 this.chargingStation
.logPrefix() +
135 ' Charging station boot notification response received: %j with undefined registration status',
141 private handleResponseHeartbeat(
142 payload
: OCPP16HeartbeatResponse
,
143 requestPayload
: OCPP16HeartbeatRequest
146 this.chargingStation
.logPrefix() +
147 ' Heartbeat response received: %j to Heartbeat request: %j',
153 private handleResponseAuthorize(
154 payload
: OCPP16AuthorizeResponse
,
155 requestPayload
: OCPP16AuthorizeRequest
157 let authorizeConnectorId
: number;
158 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
161 this.chargingStation
.getConnectorStatus(connectorId
)?.authorizeIdTag
===
164 authorizeConnectorId
= connectorId
;
168 if (payload
.idTagInfo
.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
169 this.chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= true;
171 `${this.chargingStation.logPrefix()} IdTag ${
173 } authorized on connector ${authorizeConnectorId}`
176 this.chargingStation
.getConnectorStatus(authorizeConnectorId
).idTagAuthorized
= false;
177 delete this.chargingStation
.getConnectorStatus(authorizeConnectorId
).authorizeIdTag
;
179 `${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status '${
180 payload.idTagInfo.status
181 }' on connector ${authorizeConnectorId}`
186 private async handleResponseStartTransaction(
187 payload
: OCPP16StartTransactionResponse
,
188 requestPayload
: OCPP16StartTransactionRequest
190 const connectorId
= requestPayload
.connectorId
;
192 let transactionConnectorId
: number;
193 for (const id
of this.chargingStation
.connectors
.keys()) {
194 if (id
> 0 && id
=== connectorId
) {
195 transactionConnectorId
= id
;
199 if (!transactionConnectorId
) {
201 this.chargingStation
.logPrefix() +
202 ' Trying to start a transaction on a non existing connector Id ' +
203 connectorId
.toString()
208 this.chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
&&
209 this.chargingStation
.getAuthorizeRemoteTxRequests() &&
210 this.chargingStation
.getLocalAuthListEnabled() &&
211 this.chargingStation
.hasAuthorizedTags() &&
212 !this.chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
215 this.chargingStation
.logPrefix() +
216 ' Trying to start a transaction with a not local authorized idTag ' +
217 this.chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+
218 ' on connector Id ' +
219 connectorId
.toString()
221 await this.resetConnectorOnStartTransactionError(connectorId
);
225 this.chargingStation
.getConnectorStatus(connectorId
).transactionRemoteStarted
&&
226 this.chargingStation
.getAuthorizeRemoteTxRequests() &&
227 this.chargingStation
.getMayAuthorizeAtRemoteStart() &&
228 !this.chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
&&
229 !this.chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
232 this.chargingStation
.logPrefix() +
233 ' Trying to start a transaction with a not authorized idTag ' +
234 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+
235 ' on connector Id ' +
236 connectorId
.toString()
238 await this.resetConnectorOnStartTransactionError(connectorId
);
242 this.chargingStation
.getConnectorStatus(connectorId
).idTagAuthorized
&&
243 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
!== requestPayload
.idTag
246 this.chargingStation
.logPrefix() +
247 ' Trying to start a transaction with an idTag ' +
248 requestPayload
.idTag
+
249 ' different from the authorize request one ' +
250 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
+
251 ' on connector Id ' +
252 connectorId
.toString()
254 await this.resetConnectorOnStartTransactionError(connectorId
);
258 this.chargingStation
.getConnectorStatus(connectorId
).idTagLocalAuthorized
&&
259 this.chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
!==
263 this.chargingStation
.logPrefix() +
264 ' Trying to start a transaction with an idTag ' +
265 requestPayload
.idTag
+
266 ' different from the local authorized one ' +
267 this.chargingStation
.getConnectorStatus(connectorId
).localAuthorizeIdTag
+
268 ' on connector Id ' +
269 connectorId
.toString()
271 await this.resetConnectorOnStartTransactionError(connectorId
);
274 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
276 this.chargingStation
.logPrefix() +
277 ' Trying to start a transaction on an already used connector ' +
278 connectorId
.toString() +
280 this.chargingStation
.getConnectorStatus(connectorId
)
285 this.chargingStation
.getConnectorStatus(connectorId
)?.status !==
286 OCPP16ChargePointStatus
.AVAILABLE
&&
287 this.chargingStation
.getConnectorStatus(connectorId
)?.status !==
288 OCPP16ChargePointStatus
.PREPARING
291 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
292 this.chargingStation.getConnectorStatus(connectorId)?.status
297 if (!Number.isInteger(payload
.transactionId
)) {
299 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
300 payload.transactionId
301 }, converting to integer`
303 payload
.transactionId
= Utils
.convertToInt(payload
.transactionId
);
306 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
307 this.chargingStation
.getConnectorStatus(connectorId
).transactionStarted
= true;
308 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
= payload
.transactionId
;
309 this.chargingStation
.getConnectorStatus(connectorId
).transactionIdTag
= requestPayload
.idTag
;
310 this.chargingStation
.getConnectorStatus(
312 ).transactionEnergyActiveImportRegisterValue
= 0;
313 this.chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
=
314 OCPP16ServiceUtils
.buildTransactionBeginMeterValue(
315 this.chargingStation
,
317 requestPayload
.meterStart
319 this.chargingStation
.getBeginEndMeterValues() &&
320 (await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
321 OCPP16MeterValuesRequest
,
322 OCPP16MeterValuesResponse
323 >(OCPP16RequestCommand
.METER_VALUES
, {
325 transactionId
: payload
.transactionId
,
327 this.chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
,
329 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
330 OCPP16StatusNotificationRequest
,
331 OCPP16StatusNotificationResponse
332 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
334 status: OCPP16ChargePointStatus
.CHARGING
,
335 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
337 this.chargingStation
.getConnectorStatus(connectorId
).status =
338 OCPP16ChargePointStatus
.CHARGING
;
340 this.chargingStation
.logPrefix() +
342 payload
.transactionId
.toString() +
344 this.chargingStation
.stationInfo
.chargingStationId
+
346 connectorId
.toString() +
350 if (this.chargingStation
.stationInfo
.powerSharedByConnectors
) {
351 this.chargingStation
.stationInfo
.powerDivider
++;
353 const configuredMeterValueSampleInterval
= this.chargingStation
.getConfigurationKey(
354 OCPP16StandardParametersKey
.MeterValueSampleInterval
356 this.chargingStation
.startMeterValues(
358 configuredMeterValueSampleInterval
359 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
364 this.chargingStation
.logPrefix() +
365 ' Starting transaction id ' +
366 payload
.transactionId
.toString() +
367 " REJECTED with status '" +
368 payload
?.idTagInfo
?.status +
372 await this.resetConnectorOnStartTransactionError(connectorId
);
376 private async resetConnectorOnStartTransactionError(connectorId
: number): Promise
<void> {
377 this.chargingStation
.resetConnectorStatus(connectorId
);
379 this.chargingStation
.getConnectorStatus(connectorId
).status !==
380 OCPP16ChargePointStatus
.AVAILABLE
382 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
383 OCPP16StatusNotificationRequest
,
384 OCPP16StatusNotificationResponse
385 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
387 status: OCPP16ChargePointStatus
.AVAILABLE
,
388 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
390 this.chargingStation
.getConnectorStatus(connectorId
).status =
391 OCPP16ChargePointStatus
.AVAILABLE
;
395 private async handleResponseStopTransaction(
396 payload
: OCPP16StopTransactionResponse
,
397 requestPayload
: OCPP16StopTransactionRequest
399 const transactionConnectorId
= this.chargingStation
.getConnectorIdByTransactionId(
400 requestPayload
.transactionId
402 if (!transactionConnectorId
) {
404 this.chargingStation
.logPrefix() +
405 ' Trying to stop a non existing transaction ' +
406 requestPayload
.transactionId
.toString()
410 if (payload
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
411 this.chargingStation
.getBeginEndMeterValues() &&
412 !this.chargingStation
.getOcppStrictCompliance() &&
413 this.chargingStation
.getOutOfOrderEndMeterValues() &&
414 (await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
415 OCPP16MeterValuesRequest
,
416 OCPP16MeterValuesResponse
417 >(OCPP16RequestCommand
.METER_VALUES
, {
418 connectorId
: transactionConnectorId
,
419 transactionId
: requestPayload
.transactionId
,
420 meterValue
: OCPP16ServiceUtils
.buildTransactionEndMeterValue(
421 this.chargingStation
,
422 transactionConnectorId
,
423 requestPayload
.meterStop
427 !this.chargingStation
.isChargingStationAvailable() ||
428 !this.chargingStation
.isConnectorAvailable(transactionConnectorId
)
430 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
431 OCPP16StatusNotificationRequest
,
432 OCPP16StatusNotificationResponse
433 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
434 connectorId
: transactionConnectorId
,
435 status: OCPP16ChargePointStatus
.UNAVAILABLE
,
436 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
438 this.chargingStation
.getConnectorStatus(transactionConnectorId
).status =
439 OCPP16ChargePointStatus
.UNAVAILABLE
;
441 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
442 OCPP16BootNotificationRequest
,
443 OCPP16BootNotificationResponse
444 >(OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
445 connectorId
: transactionConnectorId
,
446 status: OCPP16ChargePointStatus
.AVAILABLE
,
447 errorCode
: OCPP16ChargePointErrorCode
.NO_ERROR
,
449 this.chargingStation
.getConnectorStatus(transactionConnectorId
).status =
450 OCPP16ChargePointStatus
.AVAILABLE
;
452 if (this.chargingStation
.stationInfo
.powerSharedByConnectors
) {
453 this.chargingStation
.stationInfo
.powerDivider
--;
456 this.chargingStation
.logPrefix() +
458 requestPayload
.transactionId
.toString() +
460 this.chargingStation
.stationInfo
.chargingStationId
+
462 transactionConnectorId
.toString()
464 this.chargingStation
.resetConnectorStatus(transactionConnectorId
);
467 this.chargingStation
.logPrefix() +
468 ' Stopping transaction id ' +
469 requestPayload
.transactionId
.toString() +
470 " REJECTED with status '" +
471 payload
.idTagInfo
?.status +
477 private handleResponseStatusNotification(
478 payload
: OCPP16StatusNotificationRequest
,
479 requestPayload
: OCPP16StatusNotificationResponse
482 this.chargingStation
.logPrefix() +
483 ' Status notification response received: %j to StatusNotification request: %j',
489 private handleResponseMeterValues(
490 payload
: OCPP16MeterValuesRequest
,
491 requestPayload
: OCPP16MeterValuesResponse
494 this.chargingStation
.logPrefix() +
495 ' MeterValues response received: %j to MeterValues request: %j',