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