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