Remove string literal from log messages
[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 4
73b9adec 5import type ChargingStation from './ChargingStation';
6af9012e 6import Constants from '../utils/Constants';
a6b3c6c3 7import PerformanceStatistics from '../performance/PerformanceStatistics';
9664ec50 8import { Status } from '../types/AutomaticTransactionGenerator';
6af9012e 9import Utils from '../utils/Utils';
9f2e3130 10import logger from '../utils/Logger';
6af9012e
JB
11
12export default class AutomaticTransactionGenerator {
73b9adec 13 private static readonly instances: Map<string, AutomaticTransactionGenerator> = new Map<string, AutomaticTransactionGenerator>();
265e4266 14 public started: boolean;
9e23580d
JB
15 private readonly chargingStation: ChargingStation;
16 private readonly connectorsStatus: Map<number, Status>;
6af9012e 17
73b9adec 18 private constructor(chargingStation: ChargingStation) {
ad2f27c3 19 this.chargingStation = chargingStation;
9664ec50 20 this.connectorsStatus = new Map<number, Status>();
72740232 21 this.stopConnectors();
265e4266 22 this.started = false;
6af9012e
JB
23 }
24
73b9adec
JB
25 public static getInstance(chargingStation: ChargingStation): AutomaticTransactionGenerator {
26 if (!AutomaticTransactionGenerator.instances.has(chargingStation.id)) {
27 AutomaticTransactionGenerator.instances.set(chargingStation.id, new AutomaticTransactionGenerator(chargingStation));
28 }
29 return AutomaticTransactionGenerator.instances.get(chargingStation.id);
30 }
31
7d75bee1 32 public start(): void {
b809adf1 33 if (this.started) {
9f2e3130 34 logger.error(`${this.logPrefix()} trying to start while already started`);
b809adf1
JB
35 return;
36 }
72740232 37 this.startConnectors();
265e4266 38 this.started = true;
6af9012e
JB
39 }
40
0045cef5 41 public stop(): void {
265e4266 42 if (!this.started) {
9f2e3130 43 logger.error(`${this.logPrefix()} trying to stop while not started`);
265e4266
JB
44 return;
45 }
72740232 46 this.stopConnectors();
265e4266 47 this.started = false;
6af9012e
JB
48 }
49
72740232 50 private startConnectors(): void {
8e242273 51 if (this.connectorsStatus?.size > 0 && this.connectorsStatus.size !== this.chargingStation.getNumberOfConnectors()) {
54544ef1
JB
52 this.connectorsStatus.clear();
53 }
734d790d 54 for (const connectorId of this.chargingStation.connectors.keys()) {
72740232 55 if (connectorId > 0) {
83a3286a 56 this.startConnector(connectorId);
72740232
JB
57 }
58 }
59 }
60
61 private stopConnectors(): void {
734d790d 62 for (const connectorId of this.chargingStation.connectors.keys()) {
72740232
JB
63 if (connectorId > 0) {
64 this.stopConnector(connectorId);
65 }
66 }
67 }
68
83a3286a 69 private async internalStartConnector(connectorId: number): Promise<void> {
9664ec50 70 this.initStartConnectorStatus(connectorId);
9f2e3130 71 logger.info(this.logPrefix(connectorId) + ' started on connector and will run for ' + Utils.formatDurationMilliSeconds(this.connectorsStatus.get(connectorId).stopDate.getTime() - this.connectorsStatus.get(connectorId).startDate.getTime()));
9664ec50
JB
72 while (this.connectorsStatus.get(connectorId).start) {
73 if ((new Date()) > this.connectorsStatus.get(connectorId).stopDate) {
74 this.stopConnector(connectorId);
17991e8c
JB
75 break;
76 }
16cd35ad 77 if (!this.chargingStation.isInAcceptedState()) {
9f2e3130 78 logger.error(this.logPrefix(connectorId) + ' entered in transaction loop while the charging station is not in accepted state');
9664ec50 79 this.stopConnector(connectorId);
17991e8c
JB
80 break;
81 }
c0560973 82 if (!this.chargingStation.isChargingStationAvailable()) {
9f2e3130 83 logger.info(this.logPrefix(connectorId) + ' entered in transaction loop while the charging station is unavailable');
9664ec50 84 this.stopConnector(connectorId);
ab5f4b03
JB
85 break;
86 }
c0560973 87 if (!this.chargingStation.isConnectorAvailable(connectorId)) {
9f2e3130 88 logger.info(`${this.logPrefix(connectorId)} entered in transaction loop while the connector ${connectorId} is unavailable`);
9c7195b2 89 this.stopConnector(connectorId);
17991e8c
JB
90 break;
91 }
c0560973 92 if (!this.chargingStation?.ocppRequestService) {
9f2e3130 93 logger.info(`${this.logPrefix(connectorId)} transaction loop waiting for charging station service to be initialized`);
c0560973 94 do {
a4cc42ea 95 await Utils.sleep(Constants.CHARGING_STATION_ATG_INITIALIZATION_TIME);
c0560973
JB
96 } while (!this.chargingStation?.ocppRequestService);
97 }
72740232 98 const wait = Utils.getRandomInteger(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDelayBetweenTwoTransactions,
ad2f27c3 99 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDelayBetweenTwoTransactions) * 1000;
9f2e3130 100 logger.info(this.logPrefix(connectorId) + ' waiting for ' + Utils.formatDurationMilliSeconds(wait));
6af9012e 101 await Utils.sleep(wait);
c37528f1 102 const start = Utils.secureRandom();
ad2f27c3 103 if (start < this.chargingStation.stationInfo.AutomaticTransactionGenerator.probabilityOfStart) {
9664ec50 104 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions = 0;
6af9012e 105 // Start transaction
aef1b33a 106 const startResponse = await this.startTransaction(connectorId);
071a9315 107 this.connectorsStatus.get(connectorId).startTransactionRequests++;
ef6076c1 108 if (startResponse?.idTagInfo?.status !== AuthorizationStatus.ACCEPTED) {
9f2e3130 109 logger.warn(this.logPrefix(connectorId) + ' start transaction rejected');
071a9315 110 this.connectorsStatus.get(connectorId).rejectedStartTransactionRequests++;
6af9012e
JB
111 } else {
112 // Wait until end of transaction
72740232 113 const waitTrxEnd = Utils.getRandomInteger(this.chargingStation.stationInfo.AutomaticTransactionGenerator.maxDuration,
ad2f27c3 114 this.chargingStation.stationInfo.AutomaticTransactionGenerator.minDuration) * 1000;
9f2e3130 115 logger.info(this.logPrefix(connectorId) + ' transaction ' + this.chargingStation.getConnectorStatus(connectorId).transactionId.toString() + ' started and will stop in ' + Utils.formatDurationMilliSeconds(waitTrxEnd));
071a9315 116 this.connectorsStatus.get(connectorId).acceptedStartTransactionRequests++;
6af9012e
JB
117 await Utils.sleep(waitTrxEnd);
118 // Stop transaction
9f2e3130 119 logger.info(this.logPrefix(connectorId) + ' stop transaction ' + this.chargingStation.getConnectorStatus(connectorId).transactionId.toString());
85d20667 120 await this.stopTransaction(connectorId);
6af9012e
JB
121 }
122 } else {
9664ec50
JB
123 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions++;
124 this.connectorsStatus.get(connectorId).skippedTransactions++;
9f2e3130 125 logger.info(this.logPrefix(connectorId) + ' skipped consecutively ' + this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions.toString() + '/' + this.connectorsStatus.get(connectorId).skippedTransactions.toString() + ' transaction(s)');
6af9012e 126 }
9664ec50 127 this.connectorsStatus.get(connectorId).lastRunDate = new Date();
7d75bee1 128 }
0045cef5 129 await this.stopTransaction(connectorId);
9664ec50 130 this.connectorsStatus.get(connectorId).stoppedDate = new Date();
9f2e3130
JB
131 logger.info(this.logPrefix(connectorId) + ' stopped on connector and lasted for ' + Utils.formatDurationMilliSeconds(this.connectorsStatus.get(connectorId).stoppedDate.getTime() - this.connectorsStatus.get(connectorId).startDate.getTime()));
132 logger.debug(`${this.logPrefix(connectorId)} connector status %j`, this.connectorsStatus.get(connectorId));
6af9012e
JB
133 }
134
83a3286a
JB
135 private startConnector(connectorId: number): void {
136 // Avoid hogging the event loop with a busy loop
137 setImmediate(() => {
138 this.internalStartConnector(connectorId).catch(() => { /* This is intentional */ });
139 });
140 }
141
72740232 142 private stopConnector(connectorId: number): void {
b2bece24 143 this.connectorsStatus.set(connectorId, { ...this.connectorsStatus.get(connectorId), start: false });
9664ec50
JB
144 }
145
146 private initStartConnectorStatus(connectorId: number): void {
071a9315
JB
147 this.connectorsStatus.get(connectorId).authorizeRequests = this?.connectorsStatus.get(connectorId)?.authorizeRequests ?? 0;
148 this.connectorsStatus.get(connectorId).acceptedAuthorizeRequests = this?.connectorsStatus.get(connectorId)?.acceptedAuthorizeRequests ?? 0;
149 this.connectorsStatus.get(connectorId).rejectedAuthorizeRequests = this?.connectorsStatus.get(connectorId)?.rejectedAuthorizeRequests ?? 0;
150 this.connectorsStatus.get(connectorId).startTransactionRequests = this?.connectorsStatus.get(connectorId)?.startTransactionRequests ?? 0;
151 this.connectorsStatus.get(connectorId).acceptedStartTransactionRequests = this?.connectorsStatus.get(connectorId)?.acceptedStartTransactionRequests ?? 0;
152 this.connectorsStatus.get(connectorId).rejectedStartTransactionRequests = this?.connectorsStatus.get(connectorId)?.rejectedStartTransactionRequests ?? 0;
153 this.connectorsStatus.get(connectorId).stopTransactionRequests = this?.connectorsStatus.get(connectorId)?.stopTransactionRequests ?? 0;
9664ec50 154 this.connectorsStatus.get(connectorId).skippedConsecutiveTransactions = 0;
071a9315 155 this.connectorsStatus.get(connectorId).skippedTransactions = this?.connectorsStatus.get(connectorId)?.skippedTransactions ?? 0;
9664ec50
JB
156 const previousRunDuration = (this?.connectorsStatus.get(connectorId)?.startDate && this?.connectorsStatus.get(connectorId)?.lastRunDate)
157 ? (this.connectorsStatus.get(connectorId).lastRunDate.getTime() - this.connectorsStatus.get(connectorId).startDate.getTime())
158 : 0;
159 this.connectorsStatus.get(connectorId).startDate = new Date();
160 this.connectorsStatus.get(connectorId).stopDate = new Date(this.connectorsStatus.get(connectorId).startDate.getTime()
161 + (this.chargingStation.stationInfo?.AutomaticTransactionGenerator?.stopAfterHours ?? Constants.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS) * 3600 * 1000
162 - previousRunDuration);
163 this.connectorsStatus.get(connectorId).start = true;
72740232
JB
164 }
165
aef1b33a
JB
166 private async startTransaction(connectorId: number): Promise<StartTransactionResponse | AuthorizeResponse> {
167 const measureId = 'StartTransaction with ATG';
168 const beginId = PerformanceStatistics.beginMeasure(measureId);
169 let startResponse: StartTransactionResponse;
170 if (this.chargingStation.hasAuthorizedTags()) {
f4bf2abd 171 const idTag = this.chargingStation.getRandomIdTag();
aef1b33a 172 if (this.chargingStation.getAutomaticTransactionGeneratorRequireAuthorize()) {
f4bf2abd
JB
173 // Authorize idTag
174 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(connectorId, idTag);
071a9315 175 this.connectorsStatus.get(connectorId).authorizeRequests++;
5fdab605 176 if (authorizeResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
071a9315 177 this.connectorsStatus.get(connectorId).acceptedAuthorizeRequests++;
9f2e3130 178 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
5fdab605 179 // Start transaction
f4bf2abd 180 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId, idTag);
aef1b33a
JB
181 PerformanceStatistics.endMeasure(measureId, beginId);
182 return startResponse;
5fdab605 183 }
071a9315 184 this.connectorsStatus.get(connectorId).rejectedAuthorizeRequests++;
aef1b33a 185 PerformanceStatistics.endMeasure(measureId, beginId);
4faad557 186 return authorizeResponse;
ef6076c1 187 }
9f2e3130 188 logger.info(this.logPrefix(connectorId) + ' start transaction for idTag ' + idTag);
5fdab605 189 // Start transaction
f4bf2abd 190 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId, idTag);
aef1b33a
JB
191 PerformanceStatistics.endMeasure(measureId, beginId);
192 return startResponse;
6af9012e 193 }
9f2e3130 194 logger.info(this.logPrefix(connectorId) + ' start transaction without an idTag');
aef1b33a
JB
195 startResponse = await this.chargingStation.ocppRequestService.sendStartTransaction(connectorId);
196 PerformanceStatistics.endMeasure(measureId, beginId);
197 return startResponse;
6af9012e
JB
198 }
199
0045cef5 200 private async stopTransaction(connectorId: number, reason: StopTransactionReason = StopTransactionReason.NONE): Promise<StopTransactionResponse> {
aef1b33a
JB
201 const measureId = 'StopTransaction with ATG';
202 const beginId = PerformanceStatistics.beginMeasure(measureId);
8eb02b62 203 let transactionId = 0;
0045cef5 204 let stopResponse: StopTransactionResponse;
734d790d
JB
205 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
206 transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
0045cef5
JB
207 stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId,
208 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
209 this.chargingStation.getTransactionIdTag(transactionId),
210 reason);
071a9315 211 this.connectorsStatus.get(connectorId).stopTransactionRequests++;
0045cef5 212 } else {
9f2e3130 213 logger.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
0045cef5 214 }
aef1b33a
JB
215 PerformanceStatistics.endMeasure(measureId, beginId);
216 return stopResponse;
c0560973
JB
217 }
218
6e0964c8 219 private logPrefix(connectorId?: number): string {
c0560973 220 if (connectorId) {
54b1efe0 221 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG on connector #' + connectorId.toString() + ':');
c0560973 222 }
54b1efe0 223 return Utils.logPrefix(' ' + this.chargingStation.stationInfo.chargingStationId + ' | ATG:');
6af9012e
JB
224 }
225}