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