759f3705700d0765af848e7596f71ed05251f255
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import {
4 AuthorizeRequest,
5 OCPP16AuthorizationStatus,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionResponse,
9 StartTransactionRequest,
10 StopTransactionRequest,
11 } from '../../../types/ocpp/1.6/Transaction';
12 import {
13 HeartbeatRequest,
14 OCPP16RequestCommand,
15 StatusNotificationRequest,
16 } from '../../../types/ocpp/1.6/Requests';
17 import {
18 HeartbeatResponse,
19 OCPP16BootNotificationResponse,
20 OCPP16RegistrationStatus,
21 StatusNotificationResponse,
22 } from '../../../types/ocpp/1.6/Responses';
23 import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';
24
25 import type ChargingStation from '../../ChargingStation';
26 import { ErrorType } from '../../../types/ocpp/ErrorType';
27 import { JsonType } from '../../../types/JsonType';
28 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
29 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
30 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
31 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
32 import OCPPError from '../../../exception/OCPPError';
33 import OCPPResponseService from '../OCPPResponseService';
34 import { ResponseHandler } from '../../../types/ocpp/Responses';
35 import Utils from '../../../utils/Utils';
36 import logger from '../../../utils/Logger';
37
38 const moduleName = 'OCPP16ResponseService';
39
40 export default class OCPP16ResponseService extends OCPPResponseService {
41 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
42
43 public constructor(chargingStation: ChargingStation) {
44 if (new.target?.name === moduleName) {
45 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
46 }
47 super(chargingStation);
48 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
49 [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
50 [OCPP16RequestCommand.HEARTBEAT, this.handleResponseHeartbeat.bind(this)],
51 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this)],
52 [OCPP16RequestCommand.START_TRANSACTION, this.handleResponseStartTransaction.bind(this)],
53 [OCPP16RequestCommand.STOP_TRANSACTION, this.handleResponseStopTransaction.bind(this)],
54 [OCPP16RequestCommand.STATUS_NOTIFICATION, this.handleResponseStatusNotification.bind(this)],
55 [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
56 ]);
57 }
58
59 public async handleResponse(
60 commandName: OCPP16RequestCommand,
61 payload: JsonType | string,
62 requestPayload: JsonType
63 ): Promise<void> {
64 if (
65 this.chargingStation.isRegistered() ||
66 commandName === OCPP16RequestCommand.BOOT_NOTIFICATION
67 ) {
68 if (this.responseHandlers.has(commandName)) {
69 try {
70 await this.responseHandlers.get(commandName)(payload, requestPayload);
71 } catch (error) {
72 logger.error(
73 this.chargingStation.logPrefix() + ' Handle request response error: %j',
74 error
75 );
76 throw error;
77 }
78 } else {
79 // Throw exception
80 throw new OCPPError(
81 ErrorType.NOT_IMPLEMENTED,
82 `${commandName} is not implemented to handle request response payload ${JSON.stringify(
83 payload,
84 null,
85 2
86 )}`,
87 commandName
88 );
89 }
90 } else {
91 throw new OCPPError(
92 ErrorType.SECURITY_ERROR,
93 `${commandName} cannot be issued to handle request response payload ${JSON.stringify(
94 payload,
95 null,
96 2
97 )} while the charging station is not registered on the central server. `,
98 commandName
99 );
100 }
101 }
102
103 private handleResponseBootNotification(payload: OCPP16BootNotificationResponse): void {
104 if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
105 this.chargingStation.addConfigurationKey(
106 OCPP16StandardParametersKey.HeartBeatInterval,
107 payload.interval.toString()
108 );
109 this.chargingStation.addConfigurationKey(
110 OCPP16StandardParametersKey.HeartbeatInterval,
111 payload.interval.toString(),
112 { visible: false }
113 );
114 this.chargingStation.heartbeatSetInterval
115 ? this.chargingStation.restartHeartbeat()
116 : this.chargingStation.startHeartbeat();
117 }
118 if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) {
119 const logMsg = `${this.chargingStation.logPrefix()} Charging station in '${
120 payload.status
121 }' state on the central server`;
122 payload.status === OCPP16RegistrationStatus.REJECTED
123 ? logger.warn(logMsg)
124 : logger.info(logMsg);
125 } else {
126 logger.error(
127 this.chargingStation.logPrefix() +
128 ' Charging station boot notification response received: %j with undefined registration status',
129 payload
130 );
131 }
132 }
133
134 private handleResponseHeartbeat(
135 payload: HeartbeatResponse,
136 requestPayload: HeartbeatRequest
137 ): void {
138 logger.debug(
139 this.chargingStation.logPrefix() +
140 ' Heartbeat response received: %j to Heartbeat request: %j',
141 payload,
142 requestPayload
143 );
144 }
145
146 private handleResponseAuthorize(
147 payload: OCPP16AuthorizeResponse,
148 requestPayload: AuthorizeRequest
149 ): void {
150 let authorizeConnectorId: number;
151 for (const connectorId of this.chargingStation.connectors.keys()) {
152 if (
153 connectorId > 0 &&
154 this.chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag ===
155 requestPayload.idTag
156 ) {
157 authorizeConnectorId = connectorId;
158 break;
159 }
160 }
161 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
162 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true;
163 logger.debug(
164 `${this.chargingStation.logPrefix()} IdTag ${
165 requestPayload.idTag
166 } authorized on connector ${authorizeConnectorId}`
167 );
168 } else {
169 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
170 delete this.chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
171 logger.debug(
172 `${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status ${
173 payload.idTagInfo.status
174 } on connector ${authorizeConnectorId}`
175 );
176 }
177 }
178
179 private async handleResponseStartTransaction(
180 payload: OCPP16StartTransactionResponse,
181 requestPayload: StartTransactionRequest
182 ): Promise<void> {
183 const connectorId = requestPayload.connectorId;
184
185 let transactionConnectorId: number;
186 for (const id of this.chargingStation.connectors.keys()) {
187 if (id > 0 && id === connectorId) {
188 transactionConnectorId = id;
189 break;
190 }
191 }
192 if (!transactionConnectorId) {
193 logger.error(
194 this.chargingStation.logPrefix() +
195 ' Trying to start a transaction on a non existing connector Id ' +
196 connectorId.toString()
197 );
198 return;
199 }
200 if (
201 this.chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
202 this.chargingStation.getAuthorizeRemoteTxRequests() &&
203 this.chargingStation.getLocalAuthListEnabled() &&
204 this.chargingStation.hasAuthorizedTags() &&
205 !this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized
206 ) {
207 logger.error(
208 this.chargingStation.logPrefix() +
209 ' Trying to start a transaction with a not local authorized idTag ' +
210 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
211 ' on connector Id ' +
212 connectorId.toString()
213 );
214 await this.resetConnectorOnStartTransactionError(connectorId);
215 return;
216 }
217 if (
218 this.chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
219 this.chargingStation.getAuthorizeRemoteTxRequests() &&
220 this.chargingStation.getMayAuthorizeAtRemoteStart() &&
221 !this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
222 !this.chargingStation.getConnectorStatus(connectorId).idTagAuthorized
223 ) {
224 logger.error(
225 this.chargingStation.logPrefix() +
226 ' Trying to start a transaction with a not authorized idTag ' +
227 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
228 ' on connector Id ' +
229 connectorId.toString()
230 );
231 await this.resetConnectorOnStartTransactionError(connectorId);
232 return;
233 }
234 if (
235 this.chargingStation.getConnectorStatus(connectorId).idTagAuthorized &&
236 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag
237 ) {
238 logger.error(
239 this.chargingStation.logPrefix() +
240 ' Trying to start a transaction with an idTag ' +
241 requestPayload.idTag +
242 ' different from the authorize request one ' +
243 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
244 ' on connector Id ' +
245 connectorId.toString()
246 );
247 await this.resetConnectorOnStartTransactionError(connectorId);
248 return;
249 }
250 if (
251 this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
252 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !==
253 requestPayload.idTag
254 ) {
255 logger.error(
256 this.chargingStation.logPrefix() +
257 ' Trying to start a transaction with an idTag ' +
258 requestPayload.idTag +
259 ' different from the local authorized one ' +
260 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
261 ' on connector Id ' +
262 connectorId.toString()
263 );
264 await this.resetConnectorOnStartTransactionError(connectorId);
265 return;
266 }
267 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
268 logger.debug(
269 this.chargingStation.logPrefix() +
270 ' Trying to start a transaction on an already used connector ' +
271 connectorId.toString() +
272 ': %j',
273 this.chargingStation.getConnectorStatus(connectorId)
274 );
275 return;
276 }
277 if (
278 this.chargingStation.getConnectorStatus(connectorId)?.status !==
279 OCPP16ChargePointStatus.AVAILABLE &&
280 this.chargingStation.getConnectorStatus(connectorId)?.status !==
281 OCPP16ChargePointStatus.PREPARING
282 ) {
283 logger.error(
284 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
285 this.chargingStation.getConnectorStatus(connectorId)?.status
286 }`
287 );
288 return;
289 }
290 if (!Number.isInteger(payload.transactionId)) {
291 logger.warn(
292 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
293 payload.transactionId
294 }, converting to integer`
295 );
296 payload.transactionId = Utils.convertToInt(payload.transactionId);
297 }
298
299 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
300 this.chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
301 this.chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId;
302 this.chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag;
303 this.chargingStation.getConnectorStatus(
304 connectorId
305 ).transactionEnergyActiveImportRegisterValue = 0;
306 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue =
307 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
308 this.chargingStation,
309 connectorId,
310 requestPayload.meterStart
311 );
312 this.chargingStation.getBeginEndMeterValues() &&
313 (await this.chargingStation.ocppRequestService.sendTransactionBeginMeterValues(
314 connectorId,
315 payload.transactionId,
316 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue
317 ));
318 await this.chargingStation.ocppRequestService.sendMessageHandler(
319 OCPP16RequestCommand.STATUS_NOTIFICATION,
320 {
321 connectorId,
322 status: OCPP16ChargePointStatus.CHARGING,
323 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
324 }
325 );
326 this.chargingStation.getConnectorStatus(connectorId).status =
327 OCPP16ChargePointStatus.CHARGING;
328 logger.info(
329 this.chargingStation.logPrefix() +
330 ' Transaction ' +
331 payload.transactionId.toString() +
332 ' STARTED on ' +
333 this.chargingStation.stationInfo.chargingStationId +
334 '#' +
335 connectorId.toString() +
336 ' for idTag ' +
337 requestPayload.idTag
338 );
339 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
340 this.chargingStation.stationInfo.powerDivider++;
341 }
342 const configuredMeterValueSampleInterval = this.chargingStation.getConfigurationKey(
343 OCPP16StandardParametersKey.MeterValueSampleInterval
344 );
345 this.chargingStation.startMeterValues(
346 connectorId,
347 configuredMeterValueSampleInterval
348 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
349 : 60000
350 );
351 } else {
352 logger.warn(
353 this.chargingStation.logPrefix() +
354 ' Starting transaction id ' +
355 payload.transactionId.toString() +
356 ' REJECTED with status ' +
357 payload?.idTagInfo?.status +
358 ', idTag ' +
359 requestPayload.idTag
360 );
361 await this.resetConnectorOnStartTransactionError(connectorId);
362 }
363 }
364
365 private async resetConnectorOnStartTransactionError(connectorId: number): Promise<void> {
366 this.chargingStation.resetConnectorStatus(connectorId);
367 if (
368 this.chargingStation.getConnectorStatus(connectorId).status !==
369 OCPP16ChargePointStatus.AVAILABLE
370 ) {
371 await this.chargingStation.ocppRequestService.sendMessageHandler(
372 OCPP16RequestCommand.STATUS_NOTIFICATION,
373 {
374 connectorId,
375 status: OCPP16ChargePointStatus.AVAILABLE,
376 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
377 }
378 );
379 this.chargingStation.getConnectorStatus(connectorId).status =
380 OCPP16ChargePointStatus.AVAILABLE;
381 }
382 }
383
384 private async handleResponseStopTransaction(
385 payload: OCPP16StopTransactionResponse,
386 requestPayload: StopTransactionRequest
387 ): Promise<void> {
388 let transactionConnectorId: number;
389 for (const connectorId of this.chargingStation.connectors.keys()) {
390 if (
391 connectorId > 0 &&
392 this.chargingStation.getConnectorStatus(connectorId)?.transactionId ===
393 requestPayload.transactionId
394 ) {
395 transactionConnectorId = connectorId;
396 break;
397 }
398 }
399 if (!transactionConnectorId) {
400 logger.error(
401 this.chargingStation.logPrefix() +
402 ' Trying to stop a non existing transaction ' +
403 requestPayload.transactionId.toString()
404 );
405 return;
406 }
407 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
408 this.chargingStation.getBeginEndMeterValues() &&
409 !this.chargingStation.getOcppStrictCompliance() &&
410 this.chargingStation.getOutOfOrderEndMeterValues() &&
411 (await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(
412 transactionConnectorId,
413 requestPayload.transactionId,
414 OCPP16ServiceUtils.buildTransactionEndMeterValue(
415 this.chargingStation,
416 transactionConnectorId,
417 requestPayload.meterStop
418 )
419 ));
420 if (
421 !this.chargingStation.isChargingStationAvailable() ||
422 !this.chargingStation.isConnectorAvailable(transactionConnectorId)
423 ) {
424 await this.chargingStation.ocppRequestService.sendMessageHandler(
425 OCPP16RequestCommand.STATUS_NOTIFICATION,
426 {
427 connectorId: transactionConnectorId,
428 status: OCPP16ChargePointStatus.UNAVAILABLE,
429 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
430 }
431 );
432 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
433 OCPP16ChargePointStatus.UNAVAILABLE;
434 } else {
435 await this.chargingStation.ocppRequestService.sendMessageHandler(
436 OCPP16RequestCommand.STATUS_NOTIFICATION,
437 {
438 connectorId: transactionConnectorId,
439 status: OCPP16ChargePointStatus.AVAILABLE,
440 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
441 }
442 );
443 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
444 OCPP16ChargePointStatus.AVAILABLE;
445 }
446 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
447 this.chargingStation.stationInfo.powerDivider--;
448 }
449 logger.info(
450 this.chargingStation.logPrefix() +
451 ' Transaction ' +
452 requestPayload.transactionId.toString() +
453 ' STOPPED on ' +
454 this.chargingStation.stationInfo.chargingStationId +
455 '#' +
456 transactionConnectorId.toString()
457 );
458 this.chargingStation.resetConnectorStatus(transactionConnectorId);
459 } else {
460 logger.warn(
461 this.chargingStation.logPrefix() +
462 ' Stopping transaction id ' +
463 requestPayload.transactionId.toString() +
464 ' REJECTED with status ' +
465 payload.idTagInfo?.status
466 );
467 }
468 }
469
470 private handleResponseStatusNotification(
471 payload: StatusNotificationRequest,
472 requestPayload: StatusNotificationResponse
473 ): void {
474 logger.debug(
475 this.chargingStation.logPrefix() +
476 ' Status notification response received: %j to StatusNotification request: %j',
477 payload,
478 requestPayload
479 );
480 }
481
482 private handleResponseMeterValues(
483 payload: MeterValuesRequest,
484 requestPayload: MeterValuesResponse
485 ): void {
486 logger.debug(
487 this.chargingStation.logPrefix() +
488 ' MeterValues response received: %j to MeterValues request: %j',
489 payload,
490 requestPayload
491 );
492 }
493 }