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