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