Track ATG status on a per connector basis.
[e-mobility-charging-stations-simulator.git] / src / charging-station / AutomaticTransactionGenerator.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
c0560973 3import { AuthorizationStatus, AuthorizeResponse, StartTransactionResponse, StopTransactionReason, StopTransactionResponse } from '../types/ocpp/Transaction';
6af9012e
JB
4
5import ChargingStation from './ChargingStation';
6import Constants from '../utils/Constants';
a6b3c6c3 7import PerformanceStatistics from '../performance/PerformanceStatistics';
6af9012e
JB
8import Utils from '../utils/Utils';
9import logger from '../utils/Logger';
10
11export default class AutomaticTransactionGenerator {
265e4266 12 public started: boolean;
72740232
JB
13 private chargingStation: ChargingStation;
14 private connectorsStartStatus: Record<number, boolean>;
7d75bee1 15 private startDate!: Date;
265e4266 16 private lastRunDate!: Date;
7d75bee1 17 private stopDate!: Date;
6af9012e
JB
18
19 constructor(chargingStation: ChargingStation) {
ad2f27c3 20 this.chargingStation = chargingStation;
72740232
JB
21 this.connectorsStartStatus = {} as Record<number, boolean>;
22 this.stopConnectors();
265e4266 23 this.started = false;
6af9012e
JB
24 }
25
7d75bee1 26 public start(): void {
b809adf1
JB
27 if (this.started) {
28 logger.error(`${this.logPrefix()} trying to start while already started`);
29 return;
30 }
0045cef5 31 const previousRunDuration = (this?.startDate && this?.lastRunDate) ? (this.lastRunDate.getTime() - this.startDate.getTime()) : 0;
7d75bee1 32 this.startDate = new Date();
0045cef5 33 this.lastRunDate = this.startDate;
58fad749
JB
34 this.stopDate = new Date(this.startDate.getTime()
35 + (this.chargingStation.stationInfo?.AutomaticTransactionGenerator?.stopAfterHours ?? Constants.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS) * 3600 * 1000
0045cef5 36 - previousRunDuration);
72740232 37 this.startConnectors();
265e4266 38 this.started = true;
d7d1db72 39 logger.info(this.logPrefix() + ' started and will run for ' + Utils.formatDurationMilliSeconds(this.stopDate.getTime() - this.startDate.getTime()));
6af9012e
JB
40 }
41
0045cef5 42 public stop(): void {
265e4266
JB
43 if (!this.started) {
44 logger.error(`${this.logPrefix()} trying to stop while not started`);
45 return;
46 }
72740232 47 this.stopConnectors();
265e4266 48 this.started = false;
0045cef5 49 logger.info(`${this.logPrefix()} over and lasted for ${Utils.formatDurationMilliSeconds(this.lastRunDate.getTime() - this.startDate.getTime())}. Stopping all transactions`);
6af9012e
JB
50 }
51
72740232
JB
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> {
7d75bee1 74 logger.info(this.logPrefix(connectorId) + ' started on connector');
b322b8b4
JB
75 let skippedTransactions = 0;
76 let skippedTransactionsTotal = 0;
72740232
JB
77 this.connectorsStartStatus[connectorId] = true;
78 while (this.connectorsStartStatus[connectorId]) {
7d75bee1 79 if ((new Date()) > this.stopDate) {
0045cef5 80 this.stop();
17991e8c
JB
81 break;
82 }
c0560973
JB
83 if (!this.chargingStation.isRegistered()) {
84 logger.error(this.logPrefix(connectorId) + ' Entered in transaction loop while the charging station is not registered');
17991e8c
JB
85 break;
86 }
c0560973
JB
87 if (!this.chargingStation.isChargingStationAvailable()) {
88 logger.info(this.logPrefix(connectorId) + ' Entered in transaction loop while the charging station is unavailable');
0045cef5 89 this.stop();
ab5f4b03
JB
90 break;
91 }
c0560973
JB
92 if (!this.chargingStation.isConnectorAvailable(connectorId)) {
93 logger.info(`${this.logPrefix(connectorId)} Entered in transaction loop while the connector ${connectorId} is unavailable, stop it`);
17991e8c
JB
94 break;
95 }
c0560973
JB
96 if (!this.chargingStation?.ocppRequestService) {
97 logger.info(`${this.logPrefix(connectorId)} Transaction loop waiting for charging station service to be initialized`);
98 do {
a4cc42ea 99 await Utils.sleep(Constants.CHARGING_STATION_ATG_INITIALIZATION_TIME);
c0560973
JB
100 } while (!this.chargingStation?.ocppRequestService);
101 }
72740232 102 const wait = Utils.getRandomInteger(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDelayBetweenTwoTransactions,
ad2f27c3 103 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDelayBetweenTwoTransactions) * 1000;
d7d1db72 104 logger.info(this.logPrefix(connectorId) + ' waiting for ' + Utils.formatDurationMilliSeconds(wait));
6af9012e 105 await Utils.sleep(wait);
c37528f1 106 const start = Utils.secureRandom();
ad2f27c3 107 if (start < this.chargingStation.stationInfo.AutomaticTransactionGenerator.probabilityOfStart) {
b322b8b4 108 skippedTransactions = 0;
6af9012e 109 // Start transaction
aef1b33a 110 const startResponse = await this.startTransaction(connectorId);
ef6076c1 111 if (startResponse?.idTagInfo?.status !== AuthorizationStatus.ACCEPTED) {
54b1efe0 112 logger.warn(this.logPrefix(connectorId) + ' transaction rejected');
6af9012e
JB
113 await Utils.sleep(Constants.CHARGING_STATION_ATG_WAIT_TIME);
114 } else {
115 // Wait until end of transaction
72740232 116 const waitTrxEnd = Utils.getRandomInteger(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDuration,
ad2f27c3 117 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDuration) * 1000;
d7d1db72 118 logger.info(this.logPrefix(connectorId) + ' transaction ' + this.chargingStation.getConnector(connectorId).transactionId.toString() + ' will stop in ' + Utils.formatDurationMilliSeconds(waitTrxEnd));
6af9012e
JB
119 await Utils.sleep(waitTrxEnd);
120 // Stop transaction
85d20667
JB
121 logger.info(this.logPrefix(connectorId) + ' stop transaction ' + this.chargingStation.getConnector(connectorId).transactionId.toString());
122 await this.stopTransaction(connectorId);
6af9012e
JB
123 }
124 } else {
b322b8b4
JB
125 skippedTransactions++;
126 skippedTransactionsTotal++;
127 logger.info(this.logPrefix(connectorId) + ' skipped transaction ' + skippedTransactions.toString() + '/' + skippedTransactionsTotal.toString());
6af9012e 128 }
265e4266 129 this.lastRunDate = new Date();
7d75bee1 130 }
0045cef5 131 await this.stopTransaction(connectorId);
7d75bee1 132 logger.info(this.logPrefix(connectorId) + ' stopped on connector');
6af9012e
JB
133 }
134
72740232
JB
135 private stopConnector(connectorId: number): void {
136 this.connectorsStartStatus[connectorId] = false;
137 }
138
aef1b33a
JB
139 private async startTransaction(connectorId: number): Promise<StartTransactionResponse | AuthorizeResponse> {
140 const measureId = 'StartTransaction with ATG';
141 const beginId = PerformanceStatistics.beginMeasure(measureId);
142 let startResponse: StartTransactionResponse;
143 if (this.chargingStation.hasAuthorizedTags()) {
f4bf2abd 144 const idTag = this.chargingStation.getRandomIdTag();
aef1b33a 145 if (this.chargingStation.getAutomaticTransactionGeneratorRequireAuthorize()) {
f4bf2abd
JB
146 // Authorize idTag
147 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(connectorId, idTag);
5fdab605 148 if (authorizeResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
f4bf2abd 149 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
5fdab605 150 // Start transaction
f4bf2abd 151 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId, idTag);
aef1b33a
JB
152 PerformanceStatistics.endMeasure(measureId, beginId);
153 return startResponse;
5fdab605 154 }
aef1b33a 155 PerformanceStatistics.endMeasure(measureId, beginId);
4faad557 156 return authorizeResponse;
ef6076c1 157 }
f4bf2abd 158 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
5fdab605 159 // Start transaction
f4bf2abd 160 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId, idTag);
aef1b33a
JB
161 PerformanceStatistics.endMeasure(measureId, beginId);
162 return startResponse;
6af9012e 163 }
107efcc6 164 logger.info(this.logPrefix(connectorId) + ' start transaction without an idTag');
aef1b33a
JB
165 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId);
166 PerformanceStatistics.endMeasure(measureId, beginId);
167 return startResponse;
6af9012e
JB
168 }
169
0045cef5 170 private async stopTransaction(connectorId: number, reason: StopTransactionReason = StopTransactionReason.NONE): Promise<StopTransactionResponse> {
aef1b33a
JB
171 const measureId = 'StopTransaction with ATG';
172 const beginId = PerformanceStatistics.beginMeasure(measureId);
8eb02b62 173 let transactionId = 0;
0045cef5
JB
174 let stopResponse: StopTransactionResponse;
175 if (this.chargingStation.getConnector(connectorId)?.transactionStarted) {
8eb02b62 176 transactionId = this.chargingStation.getConnector(connectorId).transactionId;
0045cef5
JB
177 stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId,
178 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
179 this.chargingStation.getTransactionIdTag(transactionId),
180 reason);
181 } else {
8eb02b62 182 logger.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
0045cef5 183 }
aef1b33a
JB
184 PerformanceStatistics.endMeasure(measureId, beginId);
185 return stopResponse;
c0560973
JB
186 }
187
6e0964c8 188 private logPrefix(connectorId?: number): string {
c0560973 189 if (connectorId) {
54b1efe0 190 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG on connector #' + connectorId.toString() + ':');
c0560973 191 }
54b1efe0 192 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG:');
6af9012e
JB
193 }
194}