3646b89178ac1158da841dbd9073255862d61691
[e-mobility-charging-stations-simulator.git] / src / charging-station / AutomaticTransactionGenerator.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import { AuthorizationStatus, AuthorizeResponse, StartTransactionResponse, StopTransactionReason, StopTransactionResponse } from '../types/ocpp/Transaction';
4
5 import ChargingStation from './ChargingStation';
6 import Constants from '../utils/Constants';
7 import PerformanceStatistics from '../performance/PerformanceStatistics';
8 import Utils from '../utils/Utils';
9 import logger from '../utils/Logger';
10
11 export default class AutomaticTransactionGenerator {
12 public started: boolean;
13 private startDate!: Date;
14 private lastRunDate!: Date;
15 private stopDate!: Date;
16 private chargingStation: ChargingStation;
17
18 constructor(chargingStation: ChargingStation) {
19 this.chargingStation = chargingStation;
20 this.started = false;
21 }
22
23 public start(): void {
24 if (this.started) {
25 logger.error(`${this.logPrefix()} trying to start while already started`);
26 return;
27 }
28 const previousRunDuration = (this?.startDate && this?.lastRunDate) ? (this.lastRunDate.getTime() - this.startDate.getTime()) : 0;
29 this.startDate = new Date();
30 this.lastRunDate = this.startDate;
31 this.stopDate = new Date(this.startDate.getTime()
32 + (this.chargingStation.stationInfo?.AutomaticTransactionGenerator?.stopAfterHours ?? Constants.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS) * 3600 * 1000
33 - previousRunDuration);
34 this.started = true;
35 for (const connector in this.chargingStation.connectors) {
36 if (Utils.convertToInt(connector) > 0) {
37 // Avoid hogging the event loop with a busy loop
38 setImmediate(() => {
39 this.startOnConnector(Utils.convertToInt(connector)).catch(() => { /* This is intentional */ });
40 });
41 }
42 }
43 logger.info(this.logPrefix() + ' started and will run for ' + Utils.formatDurationMilliSeconds(this.stopDate.getTime() - this.startDate.getTime()));
44 }
45
46 public stop(): void {
47 if (!this.started) {
48 logger.error(`${this.logPrefix()} trying to stop while not started`);
49 return;
50 }
51 this.started = false;
52 logger.info(`${this.logPrefix()} over and lasted for ${Utils.formatDurationMilliSeconds(this.lastRunDate.getTime() - this.startDate.getTime())}. Stopping all transactions`);
53 }
54
55 private async startOnConnector(connectorId: number): Promise<void> {
56 logger.info(this.logPrefix(connectorId) + ' started on connector');
57 let skippedTransactions = 0;
58 let skippedTransactionsTotal = 0;
59 while (this.started) {
60 if ((new Date()) > this.stopDate) {
61 this.stop();
62 break;
63 }
64 if (!this.chargingStation.isRegistered()) {
65 logger.error(this.logPrefix(connectorId) + ' Entered in transaction loop while the charging station is not registered');
66 break;
67 }
68 if (!this.chargingStation.isChargingStationAvailable()) {
69 logger.info(this.logPrefix(connectorId) + ' Entered in transaction loop while the charging station is unavailable');
70 this.stop();
71 break;
72 }
73 if (!this.chargingStation.isConnectorAvailable(connectorId)) {
74 logger.info(`${this.logPrefix(connectorId)} Entered in transaction loop while the connector ${connectorId} is unavailable, stop it`);
75 break;
76 }
77 if (!this.chargingStation?.ocppRequestService) {
78 logger.info(`${this.logPrefix(connectorId)} Transaction loop waiting for charging station service to be initialized`);
79 do {
80 await Utils.sleep(Constants.CHARGING_STATION_ATG_INITIALIZATION_TIME);
81 } while (!this.chargingStation?.ocppRequestService);
82 }
83 const wait = Utils.getRandomInt(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDelayBetweenTwoTransactions,
84 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDelayBetweenTwoTransactions) * 1000;
85 logger.info(this.logPrefix(connectorId) + ' waiting for ' + Utils.formatDurationMilliSeconds(wait));
86 await Utils.sleep(wait);
87 const start = Utils.secureRandom();
88 if (start < this.chargingStation.stationInfo.AutomaticTransactionGenerator.probabilityOfStart) {
89 skippedTransactions = 0;
90 // Start transaction
91 const startResponse = await this.startTransaction(connectorId);
92 if (startResponse?.idTagInfo?.status !== AuthorizationStatus.ACCEPTED) {
93 logger.warn(this.logPrefix(connectorId) + ' transaction rejected');
94 await Utils.sleep(Constants.CHARGING_STATION_ATG_WAIT_TIME);
95 } else {
96 // Wait until end of transaction
97 const waitTrxEnd = Utils.getRandomInt(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDuration,
98 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDuration) * 1000;
99 logger.info(this.logPrefix(connectorId) + ' transaction ' + this.chargingStation.getConnector(connectorId).transactionId.toString() + ' will stop in ' + Utils.formatDurationMilliSeconds(waitTrxEnd));
100 await Utils.sleep(waitTrxEnd);
101 // Stop transaction
102 logger.info(this.logPrefix(connectorId) + ' stop transaction ' + this.chargingStation.getConnector(connectorId).transactionId.toString());
103 await this.stopTransaction(connectorId);
104 }
105 } else {
106 skippedTransactions++;
107 skippedTransactionsTotal++;
108 logger.info(this.logPrefix(connectorId) + ' skipped transaction ' + skippedTransactions.toString() + '/' + skippedTransactionsTotal.toString());
109 }
110 this.lastRunDate = new Date();
111 }
112 await this.stopTransaction(connectorId);
113 logger.info(this.logPrefix(connectorId) + ' stopped on connector');
114 }
115
116 private async startTransaction(connectorId: number): Promise<StartTransactionResponse | AuthorizeResponse> {
117 const measureId = 'StartTransaction with ATG';
118 const beginId = PerformanceStatistics.beginMeasure(measureId);
119 let startResponse: StartTransactionResponse;
120 if (this.chargingStation.hasAuthorizedTags()) {
121 const idTag = this.chargingStation.getRandomIdTag();
122 if (this.chargingStation.getAutomaticTransactionGeneratorRequireAuthorize()) {
123 // Authorize idTag
124 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(connectorId, idTag);
125 if (authorizeResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
126 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
127 // Start transaction
128 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId, idTag);
129 PerformanceStatistics.endMeasure(measureId, beginId);
130 return startResponse;
131 }
132 PerformanceStatistics.endMeasure(measureId, beginId);
133 return authorizeResponse;
134 }
135 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
136 // Start transaction
137 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId, idTag);
138 PerformanceStatistics.endMeasure(measureId, beginId);
139 return startResponse;
140 }
141 logger.info(this.logPrefix(connectorId) + ' start transaction without an idTag');
142 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId);
143 PerformanceStatistics.endMeasure(measureId, beginId);
144 return startResponse;
145 }
146
147 private async stopTransaction(connectorId: number, reason: StopTransactionReason = StopTransactionReason.NONE): Promise<StopTransactionResponse> {
148 const measureId = 'StopTransaction with ATG';
149 const beginId = PerformanceStatistics.beginMeasure(measureId);
150 let transactionId = 0;
151 let stopResponse: StopTransactionResponse;
152 if (this.chargingStation.getConnector(connectorId)?.transactionStarted) {
153 transactionId = this.chargingStation.getConnector(connectorId).transactionId;
154 stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId,
155 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
156 this.chargingStation.getTransactionIdTag(transactionId),
157 reason);
158 } else {
159 logger.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
160 }
161 PerformanceStatistics.endMeasure(measureId, beginId);
162 return stopResponse;
163 }
164
165 private logPrefix(connectorId?: number): string {
166 if (connectorId) {
167 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG on connector #' + connectorId.toString() + ':');
168 }
169 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG:');
170 }
171 }