perf: compute power limitation only once
[e-mobility-charging-stations-simulator.git] / src / charging-station / AutomaticTransactionGenerator.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
c8eeb62b 2
fcda9151
JB
3import { randomInt } from 'node:crypto'
4
66a7748d 5import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns'
be4c6702 6
66a7748d
JB
7import { BaseError } from '../exception/index.js'
8import { PerformanceStatistics } from '../performance/index.js'
e7aeea18
JB
9import {
10 AuthorizationStatus,
c004d5e2 11 ChargingStationEvents,
268a74bb 12 RequestCommand,
976d11ec
JB
13 type StartTransactionRequest,
14 type StartTransactionResponse,
268a74bb 15 type Status,
e7aeea18 16 StopTransactionReason,
66a7748d
JB
17 type StopTransactionResponse
18} from '../types/index.js'
9bf0ef23 19import {
40615072 20 clone,
4c3f6c20 21 Constants,
6dde6c5f 22 convertToDate,
9bf0ef23 23 formatDurationMilliSeconds,
5dc7c990 24 isValidDate,
9bf0ef23 25 logger,
4c3f6c20 26 logPrefix,
9bf0ef23 27 secureRandom,
66a7748d
JB
28 sleep
29} from '../utils/index.js'
4c3f6c20
JB
30import type { ChargingStation } from './ChargingStation.js'
31import { checkChargingStation } from './Helpers.js'
32import { IdTagsCache } from './IdTagsCache.js'
33import { isIdTagAuthorized } from './ocpp/index.js'
6af9012e 34
d1ff8599 35export class AutomaticTransactionGenerator {
e7aeea18 36 private static readonly instances: Map<string, AutomaticTransactionGenerator> = new Map<
66a7748d
JB
37 string,
38 AutomaticTransactionGenerator
39 >()
10068088 40
66a7748d
JB
41 public readonly connectorsStatus: Map<number, Status>
42 public started: boolean
43 private starting: boolean
44 private stopping: boolean
45 private readonly chargingStation: ChargingStation
6af9012e 46
66a7748d
JB
47 private constructor (chargingStation: ChargingStation) {
48 this.started = false
49 this.starting = false
50 this.stopping = false
51 this.chargingStation = chargingStation
52 this.connectorsStatus = new Map<number, Status>()
53 this.initializeConnectorsStatus()
6af9012e
JB
54 }
55
66a7748d
JB
56 public static getInstance (
57 chargingStation: ChargingStation
1895299d 58 ): AutomaticTransactionGenerator | undefined {
5199f9fd
JB
59 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
60 if (!AutomaticTransactionGenerator.instances.has(chargingStation.stationInfo!.hashId)) {
e7aeea18 61 AutomaticTransactionGenerator.instances.set(
5199f9fd
JB
62 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
63 chargingStation.stationInfo!.hashId,
66a7748d
JB
64 new AutomaticTransactionGenerator(chargingStation)
65 )
73b9adec 66 }
5199f9fd
JB
67 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68 return AutomaticTransactionGenerator.instances.get(chargingStation.stationInfo!.hashId)
73b9adec
JB
69 }
70
09e5a7a8
JB
71 public static deleteInstance (chargingStation: ChargingStation): boolean {
72 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
73 return AutomaticTransactionGenerator.instances.delete(chargingStation.stationInfo!.hashId)
74 }
75
e054fc1c 76 public start (stopAbsoluteDuration?: boolean): void {
66a7748d
JB
77 if (!checkChargingStation(this.chargingStation, this.logPrefix())) {
78 return
d1c6c833 79 }
66a7748d
JB
80 if (this.started) {
81 logger.warn(`${this.logPrefix()} is already started`)
82 return
b809adf1 83 }
66a7748d
JB
84 if (this.starting) {
85 logger.warn(`${this.logPrefix()} is already starting`)
86 return
11353865 87 }
66a7748d 88 this.starting = true
e054fc1c 89 this.startConnectors(stopAbsoluteDuration)
66a7748d
JB
90 this.started = true
91 this.starting = false
6af9012e
JB
92 }
93
66a7748d
JB
94 public stop (): void {
95 if (!this.started) {
96 logger.warn(`${this.logPrefix()} is already stopped`)
97 return
265e4266 98 }
66a7748d
JB
99 if (this.stopping) {
100 logger.warn(`${this.logPrefix()} is already stopping`)
101 return
11353865 102 }
66a7748d
JB
103 this.stopping = true
104 this.stopConnectors()
105 this.started = false
106 this.stopping = false
6af9012e
JB
107 }
108
e054fc1c 109 public startConnector (connectorId: number, stopAbsoluteDuration?: boolean): void {
66a7748d
JB
110 if (!checkChargingStation(this.chargingStation, this.logPrefix(connectorId))) {
111 return
d1c6c833 112 }
66a7748d
JB
113 if (!this.connectorsStatus.has(connectorId)) {
114 logger.error(`${this.logPrefix(connectorId)} starting on non existing connector`)
115 throw new BaseError(`Connector ${connectorId} does not exist`)
a5e9befc
JB
116 }
117 if (this.connectorsStatus.get(connectorId)?.start === false) {
e054fc1c 118 this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION)
ecb3869d 119 } else if (this.connectorsStatus.get(connectorId)?.start === true) {
66a7748d 120 logger.warn(`${this.logPrefix(connectorId)} is already started on connector`)
a5e9befc
JB
121 }
122 }
123
66a7748d
JB
124 public stopConnector (connectorId: number): void {
125 if (!this.connectorsStatus.has(connectorId)) {
126 logger.error(`${this.logPrefix(connectorId)} stopping on non existing connector`)
127 throw new BaseError(`Connector ${connectorId} does not exist`)
ba7965c4
JB
128 }
129 if (this.connectorsStatus.get(connectorId)?.start === true) {
66a7748d
JB
130 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
131 this.connectorsStatus.get(connectorId)!.start = false
ba7965c4 132 } else if (this.connectorsStatus.get(connectorId)?.start === false) {
66a7748d 133 logger.warn(`${this.logPrefix(connectorId)} is already stopped on connector`)
ba7965c4 134 }
a5e9befc
JB
135 }
136
e054fc1c 137 private startConnectors (stopAbsoluteDuration?: boolean): void {
e7aeea18 138 if (
5199f9fd 139 this.connectorsStatus.size > 0 &&
e7aeea18
JB
140 this.connectorsStatus.size !== this.chargingStation.getNumberOfConnectors()
141 ) {
66a7748d
JB
142 this.connectorsStatus.clear()
143 this.initializeConnectorsStatus()
54544ef1 144 }
4334db72
JB
145 if (this.chargingStation.hasEvses) {
146 for (const [evseId, evseStatus] of this.chargingStation.evses) {
147 if (evseId > 0) {
148 for (const connectorId of evseStatus.connectors.keys()) {
e054fc1c 149 this.startConnector(connectorId, stopAbsoluteDuration)
4334db72
JB
150 }
151 }
152 }
153 } else {
154 for (const connectorId of this.chargingStation.connectors.keys()) {
155 if (connectorId > 0) {
e054fc1c 156 this.startConnector(connectorId, stopAbsoluteDuration)
4334db72 157 }
72740232
JB
158 }
159 }
160 }
161
66a7748d 162 private stopConnectors (): void {
4334db72
JB
163 if (this.chargingStation.hasEvses) {
164 for (const [evseId, evseStatus] of this.chargingStation.evses) {
165 if (evseId > 0) {
166 for (const connectorId of evseStatus.connectors.keys()) {
66a7748d 167 this.stopConnector(connectorId)
4334db72
JB
168 }
169 }
170 }
171 } else {
172 for (const connectorId of this.chargingStation.connectors.keys()) {
173 if (connectorId > 0) {
66a7748d 174 this.stopConnector(connectorId)
4334db72 175 }
72740232
JB
176 }
177 }
178 }
179
e054fc1c
JB
180 private async internalStartConnector (
181 connectorId: number,
182 stopAbsoluteDuration?: boolean
183 ): Promise<void> {
184 this.setStartConnectorStatus(connectorId, stopAbsoluteDuration)
e7aeea18 185 logger.info(
44eb6026 186 `${this.logPrefix(
66a7748d 187 connectorId
9bf0ef23 188 )} started on connector and will run for ${formatDurationMilliSeconds(
66a7748d 189 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 190 this.connectorsStatus.get(connectorId)!.stopDate!.getTime() -
66a7748d
JB
191 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
192 this.connectorsStatus.get(connectorId)!.startDate!.getTime()
193 )}`
194 )
1895299d 195 while (this.connectorsStatus.get(connectorId)?.start === true) {
66a7748d
JB
196 await this.waitChargingStationAvailable(connectorId)
197 await this.waitConnectorAvailable(connectorId)
7e3bde4f 198 await this.waitRunningTransactionStopped(connectorId)
0bd926c1 199 if (!this.canStartConnector(connectorId)) {
66a7748d
JB
200 this.stopConnector(connectorId)
201 break
9b4d0c70 202 }
be4c6702 203 const wait = secondsToMilliseconds(
fcda9151 204 randomInt(
ac7f79af 205 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
fcda9151 206 ?.minDelayBetweenTwoTransactions,
ac7f79af 207 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
fcda9151 208 ?.maxDelayBetweenTwoTransactions
66a7748d
JB
209 )
210 )
211 logger.info(`${this.logPrefix(connectorId)} waiting for ${formatDurationMilliSeconds(wait)}`)
212 await sleep(wait)
213 const start = secureRandom()
ac7f79af
JB
214 if (
215 start <
5199f9fd
JB
216 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
217 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!.probabilityOfStart
ac7f79af 218 ) {
66a7748d
JB
219 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
220 this.connectorsStatus.get(connectorId)!.skippedConsecutiveTransactions = 0
6af9012e 221 // Start transaction
66a7748d 222 const startResponse = await this.startTransaction(connectorId)
5199f9fd 223 if (startResponse?.idTagInfo.status === AuthorizationStatus.ACCEPTED) {
6af9012e 224 // Wait until end of transaction
be4c6702 225 const waitTrxEnd = secondsToMilliseconds(
fcda9151
JB
226 randomInt(
227 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.minDuration,
228 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.maxDuration
66a7748d
JB
229 )
230 )
e7aeea18 231 logger.info(
a223d9be
JB
232 `${this.logPrefix(connectorId)} transaction started with id ${
233 this.chargingStation.getConnectorStatus(connectorId)?.transactionId
234 } and will stop in ${formatDurationMilliSeconds(waitTrxEnd)}`
66a7748d
JB
235 )
236 await sleep(waitTrxEnd)
237 await this.stopTransaction(connectorId)
6af9012e
JB
238 }
239 } else {
66a7748d 240 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 241 ++this.connectorsStatus.get(connectorId)!.skippedConsecutiveTransactions
66a7748d 242 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 243 ++this.connectorsStatus.get(connectorId)!.skippedTransactions
e7aeea18 244 logger.info(
a223d9be
JB
245 `${this.logPrefix(connectorId)} skipped consecutively ${
246 this.connectorsStatus.get(connectorId)?.skippedConsecutiveTransactions
247 }/${this.connectorsStatus.get(connectorId)?.skippedTransactions} transaction(s)`
66a7748d 248 )
6af9012e 249 }
66a7748d
JB
250 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
251 this.connectorsStatus.get(connectorId)!.lastRunDate = new Date()
7d75bee1 252 }
66a7748d
JB
253 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
254 this.connectorsStatus.get(connectorId)!.stoppedDate = new Date()
e7aeea18 255 logger.info(
44eb6026 256 `${this.logPrefix(
66a7748d 257 connectorId
9bf0ef23 258 )} stopped on connector and lasted for ${formatDurationMilliSeconds(
66a7748d 259 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 260 this.connectorsStatus.get(connectorId)!.stoppedDate!.getTime() -
66a7748d
JB
261 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
262 this.connectorsStatus.get(connectorId)!.startDate!.getTime()
263 )}`
264 )
e7aeea18 265 logger.debug(
c004d5e2 266 `${this.logPrefix(connectorId)} stopped with connector status: %j`,
66a7748d
JB
267 this.connectorsStatus.get(connectorId)
268 )
c004d5e2 269 this.chargingStation.emit(ChargingStationEvents.updated)
6af9012e
JB
270 }
271
e054fc1c
JB
272 private setStartConnectorStatus (
273 connectorId: number,
274 stopAbsoluteDuration = this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
275 ?.stopAbsoluteDuration
276 ): void {
66a7748d
JB
277 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
278 this.connectorsStatus.get(connectorId)!.startDate = new Date()
46a830d2 279 if (
e054fc1c 280 stopAbsoluteDuration === false ||
46a830d2 281 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5dc7c990 282 !isValidDate(this.connectorsStatus.get(connectorId)!.stopDate)
46a830d2 283 ) {
66a7748d 284 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0193fdd3
JB
285 this.connectorsStatus.get(connectorId)!.stopDate = new Date(
286 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
287 this.connectorsStatus.get(connectorId)!.startDate!.getTime() +
288 hoursToMilliseconds(
289 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
290 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!.stopAfterHours
291 )
292 )
293 }
0a1dd746
JB
294 delete this.connectorsStatus.get(connectorId)?.stoppedDate
295 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
296 this.connectorsStatus.get(connectorId)!.skippedConsecutiveTransactions = 0
66a7748d
JB
297 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
298 this.connectorsStatus.get(connectorId)!.start = true
c004d5e2 299 this.chargingStation.emit(ChargingStationEvents.updated)
4dff3039
JB
300 }
301
66a7748d
JB
302 private canStartConnector (connectorId: number): boolean {
303 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0bd926c1 304 if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) {
0a1dd746
JB
305 logger.info(
306 `${this.logPrefix(
307 connectorId
308 )} entered in transaction loop while the ATG stop date has been reached`
309 )
66a7748d 310 return false
0bd926c1 311 }
66a7748d 312 if (!this.chargingStation.inAcceptedState()) {
0bd926c1
JB
313 logger.error(
314 `${this.logPrefix(
66a7748d
JB
315 connectorId
316 )} entered in transaction loop while the charging station is not in accepted state`
317 )
318 return false
0bd926c1 319 }
66a7748d 320 if (!this.chargingStation.isChargingStationAvailable()) {
0bd926c1
JB
321 logger.info(
322 `${this.logPrefix(
66a7748d
JB
323 connectorId
324 )} entered in transaction loop while the charging station is unavailable`
325 )
326 return false
0bd926c1 327 }
66a7748d 328 if (!this.chargingStation.isConnectorAvailable(connectorId)) {
0bd926c1
JB
329 logger.info(
330 `${this.logPrefix(
66a7748d
JB
331 connectorId
332 )} entered in transaction loop while the connector ${connectorId} is unavailable`
333 )
334 return false
0bd926c1 335 }
9022112b
JB
336 const connectorStatus = this.chargingStation.getConnectorStatus(connectorId)
337 if (connectorStatus?.transactionStarted === true) {
338 logger.info(
48847bc0
JB
339 `${this.logPrefix(connectorId)} entered in transaction loop while a transaction ${
340 connectorStatus.transactionId
341 } is already started on connector ${connectorId}`
9022112b
JB
342 )
343 return false
344 }
66a7748d 345 return true
0bd926c1
JB
346 }
347
66a7748d
JB
348 private async waitChargingStationAvailable (connectorId: number): Promise<void> {
349 let logged = false
60400e23
JB
350 while (!this.chargingStation.isChargingStationAvailable()) {
351 if (!logged) {
352 logger.info(
353 `${this.logPrefix(
66a7748d
JB
354 connectorId
355 )} transaction loop waiting for charging station to be available`
356 )
357 logged = true
60400e23 358 }
7e3bde4f 359 await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
3e888c65
JB
360 }
361 }
362
66a7748d
JB
363 private async waitConnectorAvailable (connectorId: number): Promise<void> {
364 let logged = false
60400e23
JB
365 while (!this.chargingStation.isConnectorAvailable(connectorId)) {
366 if (!logged) {
367 logger.info(
368 `${this.logPrefix(
66a7748d
JB
369 connectorId
370 )} transaction loop waiting for connector ${connectorId} to be available`
371 )
372 logged = true
60400e23 373 }
7e3bde4f
JB
374 await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
375 }
376 }
377
378 private async waitRunningTransactionStopped (connectorId: number): Promise<void> {
379 const connectorStatus = this.chargingStation.getConnectorStatus(connectorId)
380 let logged = false
381 while (connectorStatus?.transactionStarted === true) {
382 if (!logged) {
383 logger.info(
48847bc0
JB
384 `${this.logPrefix(connectorId)} transaction loop waiting for started transaction ${
385 connectorStatus.transactionId
386 } on connector ${connectorId} to be stopped`
7e3bde4f
JB
387 )
388 logged = true
389 }
390 await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
3e888c65
JB
391 }
392 }
393
66a7748d 394 private initializeConnectorsStatus (): void {
4334db72
JB
395 if (this.chargingStation.hasEvses) {
396 for (const [evseId, evseStatus] of this.chargingStation.evses) {
397 if (evseId > 0) {
398 for (const connectorId of evseStatus.connectors.keys()) {
66a7748d 399 this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
4334db72
JB
400 }
401 }
402 }
403 } else {
404 for (const connectorId of this.chargingStation.connectors.keys()) {
405 if (connectorId > 0) {
66a7748d 406 this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
4334db72 407 }
4dff3039
JB
408 }
409 }
72740232
JB
410 }
411
66a7748d 412 private getConnectorStatus (connectorId: number): Status {
e3fbf1af 413 const statusIndex = connectorId - 1
a4d96807
JB
414 if (statusIndex < 0) {
415 logger.error(`${this.logPrefix(connectorId)} invalid connector id`)
416 throw new BaseError(`Invalid connector id ${connectorId}`)
417 }
e3fbf1af
JB
418 let connectorStatus: Status | undefined
419 if (this.chargingStation.getAutomaticTransactionGeneratorStatuses()?.[statusIndex] != null) {
420 connectorStatus = clone<Status>(
421 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
422 this.chargingStation.getAutomaticTransactionGeneratorStatuses()![statusIndex]
423 )
274f9b36 424 } else {
703d80dd 425 logger.warn(
48847bc0
JB
426 `${this.logPrefix(
427 connectorId
428 )} no status found for connector #${connectorId} in charging station configuration file. New status will be created`
703d80dd 429 )
e3fbf1af 430 }
6dde6c5f 431 if (connectorStatus != null) {
3423c8a5
JB
432 connectorStatus.startDate = convertToDate(connectorStatus.startDate)
433 connectorStatus.lastRunDate = convertToDate(connectorStatus.lastRunDate)
434 connectorStatus.stopDate = convertToDate(connectorStatus.stopDate)
435 connectorStatus.stoppedDate = convertToDate(connectorStatus.stoppedDate)
6dde6c5f
JB
436 if (
437 !this.started &&
438 (connectorStatus.start ||
439 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.enable !== true)
440 ) {
441 connectorStatus.start = false
442 }
443 }
5ced7e80
JB
444 return (
445 connectorStatus ?? {
446 start: false,
447 authorizeRequests: 0,
448 acceptedAuthorizeRequests: 0,
449 rejectedAuthorizeRequests: 0,
450 startTransactionRequests: 0,
451 acceptedStartTransactionRequests: 0,
452 rejectedStartTransactionRequests: 0,
453 stopTransactionRequests: 0,
454 acceptedStopTransactionRequests: 0,
455 rejectedStopTransactionRequests: 0,
456 skippedConsecutiveTransactions: 0,
66a7748d 457 skippedTransactions: 0
5ced7e80 458 }
66a7748d 459 )
5ced7e80
JB
460 }
461
66a7748d
JB
462 private async startTransaction (
463 connectorId: number
0afed85f 464 ): Promise<StartTransactionResponse | undefined> {
66a7748d
JB
465 const measureId = 'StartTransaction with ATG'
466 const beginId = PerformanceStatistics.beginMeasure(measureId)
467 let startResponse: StartTransactionResponse | undefined
f911a4af
JB
468 if (this.chargingStation.hasIdTags()) {
469 const idTag = IdTagsCache.getInstance().getIdTag(
66a7748d 470 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5199f9fd 471 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!.idTagDistribution!,
aaf2bf9c 472 this.chargingStation,
66a7748d
JB
473 connectorId
474 )
5cf9050d 475 const startTransactionLogMsg = `${this.logPrefix(
66a7748d
JB
476 connectorId
477 )} start transaction with an idTag '${idTag}'`
ccb1d6e9 478 if (this.getRequireAuthorize()) {
66a7748d 479 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 480 ++this.connectorsStatus.get(connectorId)!.authorizeRequests
041365be 481 if (await isIdTagAuthorized(this.chargingStation, connectorId, idTag)) {
66a7748d 482 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 483 ++this.connectorsStatus.get(connectorId)!.acceptedAuthorizeRequests
66a7748d 484 logger.info(startTransactionLogMsg)
5fdab605 485 // Start transaction
f7f98c68 486 startResponse = await this.chargingStation.ocppRequestService.requestHandler<
314793aa 487 Partial<StartTransactionRequest>,
66a7748d 488 StartTransactionResponse
08f130a0 489 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
ef6fa3fb 490 connectorId,
66a7748d
JB
491 idTag
492 })
493 this.handleStartTransactionResponse(connectorId, startResponse)
494 PerformanceStatistics.endMeasure(measureId, beginId)
495 return startResponse
5fdab605 496 }
66a7748d 497 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 498 ++this.connectorsStatus.get(connectorId)!.rejectedAuthorizeRequests
66a7748d
JB
499 PerformanceStatistics.endMeasure(measureId, beginId)
500 return startResponse
ef6076c1 501 }
66a7748d 502 logger.info(startTransactionLogMsg)
5fdab605 503 // Start transaction
f7f98c68 504 startResponse = await this.chargingStation.ocppRequestService.requestHandler<
314793aa 505 Partial<StartTransactionRequest>,
66a7748d 506 StartTransactionResponse
08f130a0 507 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
ef6fa3fb 508 connectorId,
66a7748d
JB
509 idTag
510 })
511 this.handleStartTransactionResponse(connectorId, startResponse)
512 PerformanceStatistics.endMeasure(measureId, beginId)
513 return startResponse
6af9012e 514 }
66a7748d 515 logger.info(`${this.logPrefix(connectorId)} start transaction without an idTag`)
f7f98c68 516 startResponse = await this.chargingStation.ocppRequestService.requestHandler<
314793aa 517 Partial<StartTransactionRequest>,
66a7748d 518 StartTransactionResponse
48847bc0
JB
519 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
520 connectorId
521 })
66a7748d
JB
522 this.handleStartTransactionResponse(connectorId, startResponse)
523 PerformanceStatistics.endMeasure(measureId, beginId)
524 return startResponse
6af9012e
JB
525 }
526
66a7748d 527 private async stopTransaction (
e7aeea18 528 connectorId: number,
66a7748d 529 reason = StopTransactionReason.LOCAL
e1d9a0f4 530 ): Promise<StopTransactionResponse | undefined> {
66a7748d
JB
531 const measureId = 'StopTransaction with ATG'
532 const beginId = PerformanceStatistics.beginMeasure(measureId)
533 let stopResponse: StopTransactionResponse | undefined
6d9876e7 534 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
49563992 535 logger.info(
a223d9be
JB
536 `${this.logPrefix(connectorId)} stop transaction with id ${
537 this.chargingStation.getConnectorStatus(connectorId)?.transactionId
538 }`
66a7748d
JB
539 )
540 stopResponse = await this.chargingStation.stopTransactionOnConnector(connectorId, reason)
541 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 542 ++this.connectorsStatus.get(connectorId)!.stopTransactionRequests
5199f9fd 543 if (stopResponse.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
66a7748d 544 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 545 ++this.connectorsStatus.get(connectorId)!.acceptedStopTransactionRequests
6d9876e7 546 } else {
66a7748d 547 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 548 ++this.connectorsStatus.get(connectorId)!.rejectedStopTransactionRequests
6d9876e7 549 }
0045cef5 550 } else {
66a7748d 551 const transactionId = this.chargingStation.getConnectorStatus(connectorId)?.transactionId
ff581359 552 logger.debug(
ba7965c4 553 `${this.logPrefix(connectorId)} stopping a not started transaction${
401fa922 554 transactionId != null ? ` with id ${transactionId}` : ''
66a7748d
JB
555 }`
556 )
0045cef5 557 }
66a7748d
JB
558 PerformanceStatistics.endMeasure(measureId, beginId)
559 return stopResponse
c0560973
JB
560 }
561
66a7748d 562 private getRequireAuthorize (): boolean {
ac7f79af
JB
563 return (
564 this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.requireAuthorize ?? true
66a7748d 565 )
ccb1d6e9
JB
566 }
567
66a7748d 568 private readonly logPrefix = (connectorId?: number): string => {
9bf0ef23 569 return logPrefix(
5199f9fd 570 ` ${this.chargingStation.stationInfo?.chargingStationId} | ATG${
401fa922 571 connectorId != null ? ` on connector #${connectorId}` : ''
66a7748d
JB
572 }:`
573 )
574 }
d9ac47ef 575
66a7748d 576 private handleStartTransactionResponse (
d9ac47ef 577 connectorId: number,
66a7748d 578 startResponse: StartTransactionResponse
d9ac47ef 579 ): void {
66a7748d 580 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 581 ++this.connectorsStatus.get(connectorId)!.startTransactionRequests
5199f9fd 582 if (startResponse.idTagInfo.status === AuthorizationStatus.ACCEPTED) {
66a7748d 583 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 584 ++this.connectorsStatus.get(connectorId)!.acceptedStartTransactionRequests
d9ac47ef 585 } else {
66a7748d
JB
586 logger.warn(`${this.logPrefix(connectorId)} start transaction rejected`)
587 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
0a1dd746 588 ++this.connectorsStatus.get(connectorId)!.rejectedStartTransactionRequests
d9ac47ef
JB
589 }
590 }
6af9012e 591}