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