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