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