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