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