Fix deprecated configuration key detection in a section
[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.sendMessageHandler(
314 OCPP16RequestCommand.METER_VALUES,
315 {
316 connectorId,
317 transactionId: payload.transactionId,
318 meterValue:
319 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue,
320 }
321 ));
322 await this.chargingStation.ocppRequestService.sendMessageHandler(
323 OCPP16RequestCommand.STATUS_NOTIFICATION,
324 {
325 connectorId,
326 status: OCPP16ChargePointStatus.CHARGING,
327 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
328 }
329 );
330 this.chargingStation.getConnectorStatus(connectorId).status =
331 OCPP16ChargePointStatus.CHARGING;
332 logger.info(
333 this.chargingStation.logPrefix() +
334 ' Transaction ' +
335 payload.transactionId.toString() +
336 ' STARTED on ' +
337 this.chargingStation.stationInfo.chargingStationId +
338 '#' +
339 connectorId.toString() +
340 ' for idTag ' +
341 requestPayload.idTag
342 );
343 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
344 this.chargingStation.stationInfo.powerDivider++;
345 }
346 const configuredMeterValueSampleInterval = this.chargingStation.getConfigurationKey(
347 OCPP16StandardParametersKey.MeterValueSampleInterval
348 );
349 this.chargingStation.startMeterValues(
350 connectorId,
351 configuredMeterValueSampleInterval
352 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
353 : 60000
354 );
355 } else {
356 logger.warn(
357 this.chargingStation.logPrefix() +
358 ' Starting transaction id ' +
359 payload.transactionId.toString() +
360 ' REJECTED with status ' +
361 payload?.idTagInfo?.status +
362 ', idTag ' +
363 requestPayload.idTag
364 );
365 await this.resetConnectorOnStartTransactionError(connectorId);
366 }
367 }
368
369 private async resetConnectorOnStartTransactionError(connectorId: number): Promise<void> {
370 this.chargingStation.resetConnectorStatus(connectorId);
371 if (
372 this.chargingStation.getConnectorStatus(connectorId).status !==
373 OCPP16ChargePointStatus.AVAILABLE
374 ) {
375 await this.chargingStation.ocppRequestService.sendMessageHandler(
376 OCPP16RequestCommand.STATUS_NOTIFICATION,
377 {
378 connectorId,
379 status: OCPP16ChargePointStatus.AVAILABLE,
380 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
381 }
382 );
383 this.chargingStation.getConnectorStatus(connectorId).status =
384 OCPP16ChargePointStatus.AVAILABLE;
385 }
386 }
387
388 private async handleResponseStopTransaction(
389 payload: OCPP16StopTransactionResponse,
390 requestPayload: StopTransactionRequest
391 ): Promise<void> {
392 let transactionConnectorId: number;
393 for (const connectorId of this.chargingStation.connectors.keys()) {
394 if (
395 connectorId > 0 &&
396 this.chargingStation.getConnectorStatus(connectorId)?.transactionId ===
397 requestPayload.transactionId
398 ) {
399 transactionConnectorId = connectorId;
400 break;
401 }
402 }
403 if (!transactionConnectorId) {
404 logger.error(
405 this.chargingStation.logPrefix() +
406 ' Trying to stop a non existing transaction ' +
407 requestPayload.transactionId.toString()
408 );
409 return;
410 }
411 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
412 this.chargingStation.getBeginEndMeterValues() &&
413 !this.chargingStation.getOcppStrictCompliance() &&
414 this.chargingStation.getOutOfOrderEndMeterValues() &&
415 (await this.chargingStation.ocppRequestService.sendMessageHandler(
416 OCPP16RequestCommand.METER_VALUES,
417 {
418 connectorId: transactionConnectorId,
419 transactionId: requestPayload.transactionId,
420 meterValue: OCPP16ServiceUtils.buildTransactionEndMeterValue(
421 this.chargingStation,
422 transactionConnectorId,
423 requestPayload.meterStop
424 ),
425 }
426 ));
427 if (
428 !this.chargingStation.isChargingStationAvailable() ||
429 !this.chargingStation.isConnectorAvailable(transactionConnectorId)
430 ) {
431 await this.chargingStation.ocppRequestService.sendMessageHandler(
432 OCPP16RequestCommand.STATUS_NOTIFICATION,
433 {
434 connectorId: transactionConnectorId,
435 status: OCPP16ChargePointStatus.UNAVAILABLE,
436 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
437 }
438 );
439 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
440 OCPP16ChargePointStatus.UNAVAILABLE;
441 } else {
442 await this.chargingStation.ocppRequestService.sendMessageHandler(
443 OCPP16RequestCommand.STATUS_NOTIFICATION,
444 {
445 connectorId: transactionConnectorId,
446 status: OCPP16ChargePointStatus.AVAILABLE,
447 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
448 }
449 );
450 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
451 OCPP16ChargePointStatus.AVAILABLE;
452 }
453 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
454 this.chargingStation.stationInfo.powerDivider--;
455 }
456 logger.info(
457 this.chargingStation.logPrefix() +
458 ' Transaction ' +
459 requestPayload.transactionId.toString() +
460 ' STOPPED on ' +
461 this.chargingStation.stationInfo.chargingStationId +
462 '#' +
463 transactionConnectorId.toString()
464 );
465 this.chargingStation.resetConnectorStatus(transactionConnectorId);
466 } else {
467 logger.warn(
468 this.chargingStation.logPrefix() +
469 ' Stopping transaction id ' +
470 requestPayload.transactionId.toString() +
471 ' REJECTED with status ' +
472 payload.idTagInfo?.status
473 );
474 }
475 }
476
477 private handleResponseStatusNotification(
478 payload: StatusNotificationRequest,
479 requestPayload: StatusNotificationResponse
480 ): void {
481 logger.debug(
482 this.chargingStation.logPrefix() +
483 ' Status notification response received: %j to StatusNotification request: %j',
484 payload,
485 requestPayload
486 );
487 }
488
489 private handleResponseMeterValues(
490 payload: MeterValuesRequest,
491 requestPayload: MeterValuesResponse
492 ): void {
493 logger.debug(
494 this.chargingStation.logPrefix() +
495 ' MeterValues response received: %j to MeterValues request: %j',
496 payload,
497 requestPayload
498 );
499 }
500 }