Commit | Line | Data |
---|---|---|
a19b897d | 1 | // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. |
c8eeb62b | 2 | |
fcda9151 JB |
3 | import { randomInt } from 'node:crypto' |
4 | ||
66a7748d | 5 | import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns' |
be4c6702 | 6 | |
66a7748d JB |
7 | import { BaseError } from '../exception/index.js' |
8 | import { PerformanceStatistics } from '../performance/index.js' | |
e7aeea18 JB |
9 | import { |
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 | 19 | import { |
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 |
30 | import type { ChargingStation } from './ChargingStation.js' |
31 | import { checkChargingStation } from './Helpers.js' | |
32 | import { IdTagsCache } from './IdTagsCache.js' | |
33 | import { isIdTagAuthorized } from './ocpp/index.js' | |
6af9012e | 34 | |
d1ff8599 | 35 | export 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 | } |