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