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