1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import { AuthorizationStatus
, AuthorizeResponse
, StartTransactionResponse
, StopTransactionReason
, StopTransactionResponse
} from
'../types/ocpp/Transaction';
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';
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
;
18 constructor(chargingStation
: ChargingStation
) {
19 this.chargingStation
= chargingStation
;
23 public start(): void {
25 logger
.error(`${this.logPrefix()} trying to start while already started`);
28 const previousRunDuration
= (this?.startDate
&& this?.lastRunDate
) ? (this.lastRunDate
.getTime() - this.startDate
.getTime()) : 0;
29 this.startDate
= new Date();
30 this.lastRunDate
= this.startDate
;
31 this.stopDate
= new Date(this.startDate
.getTime()
32 + (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
?? Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) * 3600 * 1000
33 - previousRunDuration
);
35 for (const connector
in this.chargingStation
.connectors
) {
36 if (Utils
.convertToInt(connector
) > 0) {
37 // Avoid hogging the event loop with a busy loop
39 this.startOnConnector(Utils
.convertToInt(connector
)).catch(() => { /* This is intentional */ });
43 logger
.info(this.logPrefix() + ' started and will run for ' + Utils
.formatDurationMilliSeconds(this.stopDate
.getTime() - this.startDate
.getTime()));
48 logger
.error(`${this.logPrefix()} trying to stop while not started`);
52 logger
.info(`${this.logPrefix()} over and lasted for ${Utils.formatDurationMilliSeconds(this.lastRunDate.getTime() - this.startDate.getTime())}. Stopping all transactions`);
55 private async startOnConnector(connectorId
: number): Promise
<void> {
56 logger
.info(this.logPrefix(connectorId
) + ' started on connector');
57 let skippedTransactions
= 0;
58 let skippedTransactionsTotal
= 0;
59 while (this.started
) {
60 if ((new Date()) > this.stopDate
) {
64 if (!this.chargingStation
.isRegistered()) {
65 logger
.error(this.logPrefix(connectorId
) + ' Entered in transaction loop while the charging station is not registered');
68 if (!this.chargingStation
.isChargingStationAvailable()) {
69 logger
.info(this.logPrefix(connectorId
) + ' Entered in transaction loop while the charging station is unavailable');
73 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
74 logger
.info(`${this.logPrefix(connectorId)} Entered in transaction loop while the connector ${connectorId} is unavailable, stop it`);
77 if (!this.chargingStation
?.ocppRequestService
) {
78 logger
.info(`${this.logPrefix(connectorId)} Transaction loop waiting for charging station service to be initialized`);
80 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
81 } while (!this.chargingStation
?.ocppRequestService
);
83 const wait
= Utils
.getRandomInt(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDelayBetweenTwoTransactions
,
84 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDelayBetweenTwoTransactions
) * 1000;
85 logger
.info(this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
));
86 await Utils
.sleep(wait
);
87 const start
= Utils
.secureRandom();
88 if (start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
) {
89 skippedTransactions
= 0;
91 const startResponse
= await this.startTransaction(connectorId
);
92 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
93 logger
.warn(this.logPrefix(connectorId
) + ' transaction rejected');
94 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_WAIT_TIME
);
96 // Wait until end of transaction
97 const waitTrxEnd
= Utils
.getRandomInt(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
98 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
) * 1000;
99 logger
.info(this.logPrefix(connectorId
) + ' transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString() + ' will stop in ' + Utils
.formatDurationMilliSeconds(waitTrxEnd
));
100 await Utils
.sleep(waitTrxEnd
);
102 logger
.info(this.logPrefix(connectorId
) + ' stop transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString());
103 await this.stopTransaction(connectorId
);
106 skippedTransactions
++;
107 skippedTransactionsTotal
++;
108 logger
.info(this.logPrefix(connectorId
) + ' skipped transaction ' + skippedTransactions
.toString() + '/' + skippedTransactionsTotal
.toString());
110 this.lastRunDate
= new Date();
112 await this.stopTransaction(connectorId
);
113 logger
.info(this.logPrefix(connectorId
) + ' stopped on connector');
116 private async startTransaction(connectorId
: number): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
117 const measureId
= 'StartTransaction with ATG';
118 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
119 let startResponse
: StartTransactionResponse
;
120 if (this.chargingStation
.hasAuthorizedTags()) {
121 const idTag
= this.chargingStation
.getRandomIdTag();
122 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
124 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(connectorId
, idTag
);
125 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
126 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
128 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
129 PerformanceStatistics
.endMeasure(measureId
, beginId
);
130 return startResponse
;
132 PerformanceStatistics
.endMeasure(measureId
, beginId
);
133 return authorizeResponse
;
135 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
137 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
138 PerformanceStatistics
.endMeasure(measureId
, beginId
);
139 return startResponse
;
141 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
142 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
143 PerformanceStatistics
.endMeasure(measureId
, beginId
);
144 return startResponse
;
147 private async stopTransaction(connectorId
: number, reason
: StopTransactionReason
= StopTransactionReason
.NONE
): Promise
<StopTransactionResponse
> {
148 const measureId
= 'StopTransaction with ATG';
149 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
150 let transactionId
= 0;
151 let stopResponse
: StopTransactionResponse
;
152 if (this.chargingStation
.getConnector(connectorId
)?.transactionStarted
) {
153 transactionId
= this.chargingStation
.getConnector(connectorId
).transactionId
;
154 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(transactionId
,
155 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
156 this.chargingStation
.getTransactionIdTag(transactionId
),
159 logger
.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
161 PerformanceStatistics
.endMeasure(measureId
, beginId
);
165 private logPrefix(connectorId
?: number): string {
167 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG on connector #' + connectorId
.toString() + ':');
169 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');