Add prettier configuration
[e-mobility-charging-stations-simulator.git] / src / charging-station / AutomaticTransactionGenerator.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
e7aeea18
JB
3import {
4 AuthorizationStatus,
5 AuthorizeResponse,
6 StartTransactionResponse,
7 StopTransactionReason,
8 StopTransactionResponse,
9} from '../types/ocpp/Transaction';
6af9012e 10
73b9adec 11import type ChargingStation from './ChargingStation';
6af9012e 12import Constants from '../utils/Constants';
a6b3c6c3 13import PerformanceStatistics from '../performance/PerformanceStatistics';
9664ec50 14import { Status } from '../types/AutomaticTransactionGenerator';
6af9012e 15import Utils from '../utils/Utils';
9f2e3130 16import logger from '../utils/Logger';
6af9012e
JB
17
18export default class AutomaticTransactionGenerator {
e7aeea18
JB
19 private static readonly instances: Map<string, AutomaticTransactionGenerator> = new Map<
20 string,
21 AutomaticTransactionGenerator
22 >();
265e4266 23 public started: boolean;
9e23580d
JB
24 private readonly chargingStation: ChargingStation;
25 private readonly connectorsStatus: Map<number, Status>;
6af9012e 26
73b9adec 27 private constructor(chargingStation: ChargingStation) {
ad2f27c3 28 this.chargingStation = chargingStation;
9664ec50 29 this.connectorsStatus = new Map<number, Status>();
72740232 30 this.stopConnectors();
265e4266 31 this.started = false;
6af9012e
JB
32 }
33
73b9adec
JB
34 public static getInstance(chargingStation: ChargingStation): AutomaticTransactionGenerator {
35 if (!AutomaticTransactionGenerator.instances.has(chargingStation.id)) {
e7aeea18
JB
36 AutomaticTransactionGenerator.instances.set(
37 chargingStation.id,
38 new AutomaticTransactionGenerator(chargingStation)
39 );
73b9adec
JB
40 }
41 return AutomaticTransactionGenerator.instances.get(chargingStation.id);
42 }
43
7d75bee1 44 public start(): void {
b809adf1 45 if (this.started) {
9f2e3130 46 logger.error(`${this.logPrefix()} trying to start while already started`);
b809adf1
JB
47 return;
48 }
72740232 49 this.startConnectors();
265e4266 50 this.started = true;
6af9012e
JB
51 }
52
0045cef5 53 public stop(): void {
265e4266 54 if (!this.started) {
9f2e3130 55 logger.error(`${this.logPrefix()} trying to stop while not started`);
265e4266
JB
56 return;
57 }
72740232 58 this.stopConnectors();
265e4266 59 this.started = false;
6af9012e
JB
60 }
61
72740232 62 private startConnectors(): void {
e7aeea18
JB
63 if (
64 this.connectorsStatus?.size > 0 &&
65 this.connectorsStatus.size !== this.chargingStation.getNumberOfConnectors()
66 ) {
54544ef1
JB
67 this.connectorsStatus.clear();
68 }
734d790d 69 for (const connectorId of this.chargingStation.connectors.keys()) {
72740232 70 if (connectorId > 0) {
83a3286a 71 this.startConnector(connectorId);
72740232
JB
72 }
73 }
74 }
75
76 private stopConnectors(): void {
734d790d 77 for (const connectorId of this.chargingStation.connectors.keys()) {
72740232
JB
78 if (connectorId > 0) {
79 this.stopConnector(connectorId);
80 }
81 }
82 }
83
83a3286a 84 private async internalStartConnector(connectorId: number): Promise<void> {
9664ec50 85 this.initStartConnectorStatus(connectorId);
e7aeea18
JB
86 logger.info(
87 this.logPrefix(connectorId) +
88 ' started on connector and will run for ' +
89 Utils.formatDurationMilliSeconds(
90 this.connectorsStatus.get(connectorId).stopDate.getTime() -
91 this.connectorsStatus.get(connectorId).startDate.getTime()
92 )
93 );
9664ec50 94 while (this.connectorsStatus.get(connectorId).start) {
e7aeea18 95 if (new Date() > this.connectorsStatus.get(connectorId).stopDate) {
9664ec50 96 this.stopConnector(connectorId);
17991e8c
JB
97 break;
98 }
16cd35ad 99 if (!this.chargingStation.isInAcceptedState()) {
e7aeea18
JB
100 logger.error(
101 this.logPrefix(connectorId) +
102 ' entered in transaction loop while the charging station is not in accepted state'
103 );
9664ec50 104 this.stopConnector(connectorId);
17991e8c
JB
105 break;
106 }
c0560973 107 if (!this.chargingStation.isChargingStationAvailable()) {
e7aeea18
JB
108 logger.info(
109 this.logPrefix(connectorId) +
110 ' entered in transaction loop while the charging station is unavailable'
111 );
9664ec50 112 this.stopConnector(connectorId);
ab5f4b03
JB
113 break;
114 }
c0560973 115 if (!this.chargingStation.isConnectorAvailable(connectorId)) {
e7aeea18
JB
116 logger.info(
117 `${this.logPrefix(
118 connectorId
119 )} entered in transaction loop while the connector ${connectorId} is unavailable`
120 );
9c7195b2 121 this.stopConnector(connectorId);
17991e8c
JB
122 break;
123 }
c0560973 124 if (!this.chargingStation?.ocppRequestService) {
e7aeea18
JB
125 logger.info(
126 `${this.logPrefix(
127 connectorId
128 )} transaction loop waiting for charging station service to be initialized`
129 );
c0560973 130 do {
a4cc42ea 131 await Utils.sleep(Constants.CHARGING_STATION_ATG_INITIALIZATION_TIME);
c0560973
JB
132 } while (!this.chargingStation?.ocppRequestService);
133 }
e7aeea18
JB
134 const wait =
135 Utils.getRandomInteger(
136 this.chargingStation.stationInfo.AutomaticTransactionGenerator
137 .maxDelayBetweenTwoTransactions,
138 this.chargingStation.stationInfo.AutomaticTransactionGenerator
139 .minDelayBetweenTwoTransactions
140 ) * 1000;
141 logger.info(
142 this.logPrefix(connectorId) + ' waiting for ' + Utils.formatDurationMilliSeconds(wait)
143 );
6af9012e 144 await Utils.sleep(wait);
c37528f1 145 const start = Utils.secureRandom();
e7aeea18
JB
146 if (
147 start < this.chargingStation.stationInfo.AutomaticTransactionGenerator.probabilityOfStart
148 ) {
9664ec50 149 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions = 0;
6af9012e 150 // Start transaction
aef1b33a 151 const startResponse = await this.startTransaction(connectorId);
071a9315 152 this.connectorsStatus.get(connectorId).startTransactionRequests++;
ef6076c1 153 if (startResponse?.idTagInfo?.status !== AuthorizationStatus.ACCEPTED) {
9f2e3130 154 logger.warn(this.logPrefix(connectorId) + ' start transaction rejected');
071a9315 155 this.connectorsStatus.get(connectorId).rejectedStartTransactionRequests++;
6af9012e
JB
156 } else {
157 // Wait until end of transaction
e7aeea18
JB
158 const waitTrxEnd =
159 Utils.getRandomInteger(
160 this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDuration,
161 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDuration
162 ) * 1000;
163 logger.info(
164 this.logPrefix(connectorId) +
165 ' transaction ' +
166 this.chargingStation.getConnectorStatus(connectorId).transactionId.toString() +
167 ' started and will stop in ' +
168 Utils.formatDurationMilliSeconds(waitTrxEnd)
169 );
071a9315 170 this.connectorsStatus.get(connectorId).acceptedStartTransactionRequests++;
6af9012e
JB
171 await Utils.sleep(waitTrxEnd);
172 // Stop transaction
e7aeea18
JB
173 logger.info(
174 this.logPrefix(connectorId) +
175 ' stop transaction ' +
176 this.chargingStation.getConnectorStatus(connectorId).transactionId.toString()
177 );
85d20667 178 await this.stopTransaction(connectorId);
6af9012e
JB
179 }
180 } else {
9664ec50
JB
181 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions++;
182 this.connectorsStatus.get(connectorId).skippedTransactions++;
e7aeea18
JB
183 logger.info(
184 this.logPrefix(connectorId) +
185 ' skipped consecutively ' +
186 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions.toString() +
187 '/' +
188 this.connectorsStatus.get(connectorId).skippedTransactions.toString() +
189 ' transaction(s)'
190 );
6af9012e 191 }
9664ec50 192 this.connectorsStatus.get(connectorId).lastRunDate = new Date();
7d75bee1 193 }
0045cef5 194 await this.stopTransaction(connectorId);
9664ec50 195 this.connectorsStatus.get(connectorId).stoppedDate = new Date();
e7aeea18
JB
196 logger.info(
197 this.logPrefix(connectorId) +
198 ' stopped on connector and lasted for ' +
199 Utils.formatDurationMilliSeconds(
200 this.connectorsStatus.get(connectorId).stoppedDate.getTime() -
201 this.connectorsStatus.get(connectorId).startDate.getTime()
202 )
203 );
204 logger.debug(
205 `${this.logPrefix(connectorId)} connector status %j`,
206 this.connectorsStatus.get(connectorId)
207 );
6af9012e
JB
208 }
209
83a3286a
JB
210 private startConnector(connectorId: number): void {
211 // Avoid hogging the event loop with a busy loop
212 setImmediate(() => {
e7aeea18
JB
213 this.internalStartConnector(connectorId).catch(() => {
214 /* This is intentional */
215 });
83a3286a
JB
216 });
217 }
218
72740232 219 private stopConnector(connectorId: number): void {
e7aeea18
JB
220 this.connectorsStatus.set(connectorId, {
221 ...this.connectorsStatus.get(connectorId),
222 start: false,
223 });
9664ec50
JB
224 }
225
226 private initStartConnectorStatus(connectorId: number): void {
e7aeea18
JB
227 this.connectorsStatus.get(connectorId).authorizeRequests =
228 this?.connectorsStatus.get(connectorId)?.authorizeRequests ?? 0;
229 this.connectorsStatus.get(connectorId).acceptedAuthorizeRequests =
230 this?.connectorsStatus.get(connectorId)?.acceptedAuthorizeRequests ?? 0;
231 this.connectorsStatus.get(connectorId).rejectedAuthorizeRequests =
232 this?.connectorsStatus.get(connectorId)?.rejectedAuthorizeRequests ?? 0;
233 this.connectorsStatus.get(connectorId).startTransactionRequests =
234 this?.connectorsStatus.get(connectorId)?.startTransactionRequests ?? 0;
235 this.connectorsStatus.get(connectorId).acceptedStartTransactionRequests =
236 this?.connectorsStatus.get(connectorId)?.acceptedStartTransactionRequests ?? 0;
237 this.connectorsStatus.get(connectorId).rejectedStartTransactionRequests =
238 this?.connectorsStatus.get(connectorId)?.rejectedStartTransactionRequests ?? 0;
239 this.connectorsStatus.get(connectorId).stopTransactionRequests =
240 this?.connectorsStatus.get(connectorId)?.stopTransactionRequests ?? 0;
9664ec50 241 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions = 0;
e7aeea18
JB
242 this.connectorsStatus.get(connectorId).skippedTransactions =
243 this?.connectorsStatus.get(connectorId)?.skippedTransactions ?? 0;
244 const previousRunDuration =
245 this?.connectorsStatus.get(connectorId)?.startDate &&
246 this?.connectorsStatus.get(connectorId)?.lastRunDate
247 ? this.connectorsStatus.get(connectorId).lastRunDate.getTime() -
248 this.connectorsStatus.get(connectorId).startDate.getTime()
249 : 0;
9664ec50 250 this.connectorsStatus.get(connectorId).startDate = new Date();
e7aeea18
JB
251 this.connectorsStatus.get(connectorId).stopDate = new Date(
252 this.connectorsStatus.get(connectorId).startDate.getTime() +
253 (this.chargingStation.stationInfo?.AutomaticTransactionGenerator?.stopAfterHours ??
254 Constants.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS) *
255 3600 *
256 1000 -
257 previousRunDuration
258 );
9664ec50 259 this.connectorsStatus.get(connectorId).start = true;
72740232
JB
260 }
261
e7aeea18
JB
262 private async startTransaction(
263 connectorId: number
264 ): Promise<StartTransactionResponse | AuthorizeResponse> {
aef1b33a
JB
265 const measureId = 'StartTransaction with ATG';
266 const beginId = PerformanceStatistics.beginMeasure(measureId);
267 let startResponse: StartTransactionResponse;
268 if (this.chargingStation.hasAuthorizedTags()) {
f4bf2abd 269 const idTag = this.chargingStation.getRandomIdTag();
aef1b33a 270 if (this.chargingStation.getAutomaticTransactionGeneratorRequireAuthorize()) {
f4bf2abd 271 // Authorize idTag
e7aeea18
JB
272 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(
273 connectorId,
274 idTag
275 );
071a9315 276 this.connectorsStatus.get(connectorId).authorizeRequests++;
5fdab605 277 if (authorizeResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
071a9315 278 this.connectorsStatus.get(connectorId).acceptedAuthorizeRequests++;
9f2e3130 279 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
5fdab605 280 // Start transaction
e7aeea18
JB
281 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(
282 connectorId,
283 idTag
284 );
aef1b33a
JB
285 PerformanceStatistics.endMeasure(measureId, beginId);
286 return startResponse;
5fdab605 287 }
071a9315 288 this.connectorsStatus.get(connectorId).rejectedAuthorizeRequests++;
aef1b33a 289 PerformanceStatistics.endMeasure(measureId, beginId);
4faad557 290 return authorizeResponse;
ef6076c1 291 }
9f2e3130 292 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
5fdab605 293 // Start transaction
e7aeea18
JB
294 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(
295 connectorId,
296 idTag
297 );
aef1b33a
JB
298 PerformanceStatistics.endMeasure(measureId, beginId);
299 return startResponse;
6af9012e 300 }
9f2e3130 301 logger.info(this.logPrefix(connectorId) + ' start transaction without an idTag');
aef1b33a
JB
302 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId);
303 PerformanceStatistics.endMeasure(measureId, beginId);
304 return startResponse;
6af9012e
JB
305 }
306
e7aeea18
JB
307 private async stopTransaction(
308 connectorId: number,
309 reason: StopTransactionReason = StopTransactionReason.NONE
310 ): Promise<StopTransactionResponse> {
aef1b33a
JB
311 const measureId = 'StopTransaction with ATG';
312 const beginId = PerformanceStatistics.beginMeasure(measureId);
8eb02b62 313 let transactionId = 0;
0045cef5 314 let stopResponse: StopTransactionResponse;
734d790d
JB
315 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
316 transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
e7aeea18
JB
317 stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(
318 transactionId,
0045cef5
JB
319 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
320 this.chargingStation.getTransactionIdTag(transactionId),
e7aeea18
JB
321 reason
322 );
071a9315 323 this.connectorsStatus.get(connectorId).stopTransactionRequests++;
0045cef5 324 } else {
e7aeea18
JB
325 logger.warn(
326 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
327 transactionId ? ' ' + transactionId.toString() : ''
328 }`
329 );
0045cef5 330 }
aef1b33a
JB
331 PerformanceStatistics.endMeasure(measureId, beginId);
332 return stopResponse;
c0560973
JB
333 }
334
6e0964c8 335 private logPrefix(connectorId?: number): string {
c0560973 336 if (connectorId) {
e7aeea18
JB
337 return Utils.logPrefix(
338 ' ' +
339 this.chargingStation.stationInfo.chargingStationId +
340 ' | ATG on connector #' +
341 connectorId.toString() +
342 ':'
343 );
c0560973 344 }
54b1efe0 345 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG:');
6af9012e
JB
346 }
347}