Add persistent OCPP parameters key/value support by CS generated name
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
e7aeea18
JB
3import {
4 AuthorizeRequest,
5 OCPP16AuthorizationStatus,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionResponse,
9 StartTransactionRequest,
10 StopTransactionRequest,
11} from '../../../types/ocpp/1.6/Transaction';
12import {
13 HeartbeatRequest,
14 OCPP16RequestCommand,
15 StatusNotificationRequest,
16} from '../../../types/ocpp/1.6/Requests';
17import {
18 HeartbeatResponse,
19 OCPP16BootNotificationResponse,
20 OCPP16RegistrationStatus,
21 StatusNotificationResponse,
22} from '../../../types/ocpp/1.6/Responses';
c0560973
JB
23import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';
24
73b9adec 25import type ChargingStation from '../../ChargingStation';
a4bc2942 26import { ErrorType } from '../../../types/ocpp/ErrorType';
d1888640 27import { JsonType } from '../../../types/JsonType';
93b4a429 28import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
c0560973 29import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
fd0c36fa 30import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
c0560973 31import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
e58068fd 32import OCPPError from '../../../exception/OCPPError';
c0560973 33import OCPPResponseService from '../OCPPResponseService';
74c6a954 34import { ResponseHandler } from '../../../types/ocpp/Responses';
c0560973 35import Utils from '../../../utils/Utils';
9f2e3130 36import logger from '../../../utils/Logger';
c0560973 37
909dcf2d
JB
38const moduleName = 'OCPP16ResponseService';
39
c0560973 40export default class OCPP16ResponseService extends OCPPResponseService {
58144adb
JB
41 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
42
9f2e3130 43 public constructor(chargingStation: ChargingStation) {
909dcf2d 44 if (new.target?.name === moduleName) {
06127450 45 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 46 }
58144adb
JB
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)],
e7aeea18 55 [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
58144adb
JB
56 ]);
57 }
58
e7aeea18
JB
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 ) {
124f3553
JB
68 if (this.responseHandlers.has(commandName)) {
69 try {
70 await this.responseHandlers.get(commandName)(payload, requestPayload);
71 } catch (error) {
e7aeea18
JB
72 logger.error(
73 this.chargingStation.logPrefix() + ' Handle request response error: %j',
74 error
75 );
124f3553
JB
76 throw error;
77 }
78 } else {
79 // Throw exception
e7aeea18
JB
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 );
887fef76 89 }
c0560973 90 } else {
e7aeea18
JB
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 );
c0560973
JB
100 }
101 }
102
624df199 103 private handleResponseBootNotification(payload: OCPP16BootNotificationResponse): void {
c0560973 104 if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
e7aeea18
JB
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();
672fed6e 117 }
74c6a954 118 if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) {
e7aeea18
JB
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);
c0560973 125 } else {
e7aeea18
JB
126 logger.error(
127 this.chargingStation.logPrefix() +
128 ' Charging station boot notification response received: %j with undefined registration status',
129 payload
130 );
c0560973
JB
131 }
132 }
133
e7aeea18
JB
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 );
58144adb
JB
144 }
145
e7aeea18
JB
146 private handleResponseAuthorize(
147 payload: OCPP16AuthorizeResponse,
148 requestPayload: AuthorizeRequest
149 ): void {
58144adb 150 let authorizeConnectorId: number;
734d790d 151 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
152 if (
153 connectorId > 0 &&
154 this.chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag ===
155 requestPayload.idTag
156 ) {
734d790d 157 authorizeConnectorId = connectorId;
58144adb
JB
158 break;
159 }
160 }
161 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
a2653482 162 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true;
e7aeea18
JB
163 logger.debug(
164 `${this.chargingStation.logPrefix()} IdTag ${
165 requestPayload.idTag
166 } authorized on connector ${authorizeConnectorId}`
167 );
58144adb 168 } else {
a2653482 169 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
734d790d 170 delete this.chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
e7aeea18
JB
171 logger.debug(
172 `${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status ${
173 payload.idTagInfo.status
174 } on connector ${authorizeConnectorId}`
175 );
58144adb
JB
176 }
177 }
178
e7aeea18
JB
179 private async handleResponseStartTransaction(
180 payload: OCPP16StartTransactionResponse,
181 requestPayload: StartTransactionRequest
182 ): Promise<void> {
c0560973
JB
183 const connectorId = requestPayload.connectorId;
184
185 let transactionConnectorId: number;
734d790d
JB
186 for (const id of this.chargingStation.connectors.keys()) {
187 if (id > 0 && id === connectorId) {
188 transactionConnectorId = id;
c0560973
JB
189 break;
190 }
191 }
192 if (!transactionConnectorId) {
e7aeea18
JB
193 logger.error(
194 this.chargingStation.logPrefix() +
195 ' Trying to start a transaction on a non existing connector Id ' +
196 connectorId.toString()
197 );
c0560973
JB
198 return;
199 }
e7aeea18
JB
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 );
a2653482
JB
214 await this.resetConnectorOnStartTransactionError(connectorId);
215 return;
216 }
e7aeea18
JB
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 );
a2653482
JB
231 await this.resetConnectorOnStartTransactionError(connectorId);
232 return;
233 }
e7aeea18
JB
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 );
a2653482
JB
247 await this.resetConnectorOnStartTransactionError(connectorId);
248 return;
249 }
e7aeea18
JB
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 );
a2653482 264 await this.resetConnectorOnStartTransactionError(connectorId);
163547b1
JB
265 return;
266 }
734d790d 267 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
e7aeea18
JB
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 );
c0560973
JB
275 return;
276 }
e7aeea18
JB
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 );
290d006c
JB
288 return;
289 }
f3a6f63b 290 if (!Number.isInteger(payload.transactionId)) {
e7aeea18
JB
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 );
f3a6f63b
JB
296 payload.transactionId = Utils.convertToInt(payload.transactionId);
297 }
c0560973 298
f0c6ed89 299 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
734d790d
JB
300 this.chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
301 this.chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId;
302 this.chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag;
e7aeea18
JB
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() &&
443c0641
JB
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 }
e7aeea18 321 ));
93b4a429
JB
322 await this.chargingStation.ocppRequestService.sendMessageHandler(
323 OCPP16RequestCommand.STATUS_NOTIFICATION,
324 {
325 connectorId,
326 status: OCPP16ChargePointStatus.CHARGING,
327 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
328 }
e7aeea18
JB
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 );
c0560973
JB
343 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
344 this.chargingStation.stationInfo.powerDivider++;
345 }
e7aeea18
JB
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 );
c0560973 355 } else {
e7aeea18
JB
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 );
a2653482
JB
365 await this.resetConnectorOnStartTransactionError(connectorId);
366 }
367 }
368
369 private async resetConnectorOnStartTransactionError(connectorId: number): Promise<void> {
370 this.chargingStation.resetConnectorStatus(connectorId);
e7aeea18
JB
371 if (
372 this.chargingStation.getConnectorStatus(connectorId).status !==
373 OCPP16ChargePointStatus.AVAILABLE
374 ) {
93b4a429
JB
375 await this.chargingStation.ocppRequestService.sendMessageHandler(
376 OCPP16RequestCommand.STATUS_NOTIFICATION,
377 {
378 connectorId,
379 status: OCPP16ChargePointStatus.AVAILABLE,
380 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
381 }
e7aeea18
JB
382 );
383 this.chargingStation.getConnectorStatus(connectorId).status =
384 OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
385 }
386 }
387
e7aeea18
JB
388 private async handleResponseStopTransaction(
389 payload: OCPP16StopTransactionResponse,
390 requestPayload: StopTransactionRequest
391 ): Promise<void> {
f479a792
JB
392 const transactionConnectorId = this.chargingStation.getConnectorIdByTransactionId(
393 requestPayload.transactionId
394 );
c0560973 395 if (!transactionConnectorId) {
e7aeea18
JB
396 logger.error(
397 this.chargingStation.logPrefix() +
398 ' Trying to stop a non existing transaction ' +
399 requestPayload.transactionId.toString()
400 );
c0560973
JB
401 return;
402 }
403 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
e7aeea18
JB
404 this.chargingStation.getBeginEndMeterValues() &&
405 !this.chargingStation.getOcppStrictCompliance() &&
406 this.chargingStation.getOutOfOrderEndMeterValues() &&
3a33b6a9
JB
407 (await this.chargingStation.ocppRequestService.sendMessageHandler(
408 OCPP16RequestCommand.METER_VALUES,
409 {
410 connectorId: transactionConnectorId,
411 transactionId: requestPayload.transactionId,
412 meterValue: OCPP16ServiceUtils.buildTransactionEndMeterValue(
413 this.chargingStation,
414 transactionConnectorId,
415 requestPayload.meterStop
416 ),
417 }
e7aeea18
JB
418 ));
419 if (
420 !this.chargingStation.isChargingStationAvailable() ||
421 !this.chargingStation.isConnectorAvailable(transactionConnectorId)
422 ) {
93b4a429
JB
423 await this.chargingStation.ocppRequestService.sendMessageHandler(
424 OCPP16RequestCommand.STATUS_NOTIFICATION,
425 {
426 connectorId: transactionConnectorId,
427 status: OCPP16ChargePointStatus.UNAVAILABLE,
428 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
429 }
e7aeea18
JB
430 );
431 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
432 OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 433 } else {
93b4a429
JB
434 await this.chargingStation.ocppRequestService.sendMessageHandler(
435 OCPP16RequestCommand.STATUS_NOTIFICATION,
436 {
437 connectorId: transactionConnectorId,
438 status: OCPP16ChargePointStatus.AVAILABLE,
439 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
440 }
e7aeea18
JB
441 );
442 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
443 OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
444 }
445 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
446 this.chargingStation.stationInfo.powerDivider--;
447 }
e7aeea18
JB
448 logger.info(
449 this.chargingStation.logPrefix() +
450 ' Transaction ' +
451 requestPayload.transactionId.toString() +
452 ' STOPPED on ' +
453 this.chargingStation.stationInfo.chargingStationId +
454 '#' +
455 transactionConnectorId.toString()
456 );
a2653482 457 this.chargingStation.resetConnectorStatus(transactionConnectorId);
c0560973 458 } else {
e7aeea18
JB
459 logger.warn(
460 this.chargingStation.logPrefix() +
461 ' Stopping transaction id ' +
462 requestPayload.transactionId.toString() +
463 ' REJECTED with status ' +
464 payload.idTagInfo?.status
465 );
c0560973
JB
466 }
467 }
468
e7aeea18
JB
469 private handleResponseStatusNotification(
470 payload: StatusNotificationRequest,
471 requestPayload: StatusNotificationResponse
472 ): void {
473 logger.debug(
474 this.chargingStation.logPrefix() +
475 ' Status notification response received: %j to StatusNotification request: %j',
476 payload,
477 requestPayload
478 );
c0560973
JB
479 }
480
e7aeea18
JB
481 private handleResponseMeterValues(
482 payload: MeterValuesRequest,
483 requestPayload: MeterValuesResponse
484 ): void {
485 logger.debug(
486 this.chargingStation.logPrefix() +
487 ' MeterValues response received: %j to MeterValues request: %j',
488 payload,
489 requestPayload
490 );
c0560973 491 }
c0560973 492}