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 chargingStation
: ChargingStation
;
14 private connectorsStartStatus
: Record
<number, boolean>;
15 private startDate
!: Date;
16 private lastRunDate
!: Date;
17 private stopDate
!: Date;
19 constructor(chargingStation
: ChargingStation
) {
20 this.chargingStation
= chargingStation
;
21 this.connectorsStartStatus
= {} as Record
<number, boolean>;
22 this.stopConnectors();
26 public start(): void {
28 logger
.error(`${this.logPrefix()} trying to start while already started`);
31 const previousRunDuration
= (this?.startDate
&& this?.lastRunDate
) ? (this.lastRunDate
.getTime() - this.startDate
.getTime()) : 0;
32 this.startDate
= new Date();
33 this.lastRunDate
= this.startDate
;
34 this.stopDate
= new Date(this.startDate
.getTime()
35 + (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
?? Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) * 3600 * 1000
36 - previousRunDuration
);
37 this.startConnectors();
39 logger
.info(this.logPrefix() + ' started and will run for ' + Utils
.formatDurationMilliSeconds(this.stopDate
.getTime() - this.startDate
.getTime()));
44 logger
.error(`${this.logPrefix()} trying to stop while not started`);
47 this.stopConnectors();
49 logger
.info(`${this.logPrefix()} over and lasted for ${Utils.formatDurationMilliSeconds(this.lastRunDate.getTime() - this.startDate.getTime())}. Stopping all transactions`);
52 private startConnectors(): void {
53 for (const connector
in this.chargingStation
.connectors
) {
54 const connectorId
= Utils
.convertToInt(connector
);
55 if (connectorId
> 0) {
56 // Avoid hogging the event loop with a busy loop
58 this.startConnector(connectorId
).catch(() => { /* This is intentional */ });
64 private stopConnectors(): void {
65 for (const connector
in this.chargingStation
.connectors
) {
66 const connectorId
= Utils
.convertToInt(connector
);
67 if (connectorId
> 0) {
68 this.stopConnector(connectorId
);
73 private async startConnector(connectorId
: number): Promise
<void> {
74 logger
.info(this.logPrefix(connectorId
) + ' started on connector');
75 let skippedTransactions
= 0;
76 let skippedTransactionsTotal
= 0;
77 this.connectorsStartStatus
[connectorId
] = true;
78 while (this.connectorsStartStatus
[connectorId
]) {
79 if ((new Date()) > this.stopDate
) {
83 if (!this.chargingStation
.isRegistered()) {
84 logger
.error(this.logPrefix(connectorId
) + ' Entered in transaction loop while the charging station is not registered');
87 if (!this.chargingStation
.isChargingStationAvailable()) {
88 logger
.info(this.logPrefix(connectorId
) + ' Entered in transaction loop while the charging station is unavailable');
92 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
93 logger
.info(`${this.logPrefix(connectorId)} Entered in transaction loop while the connector ${connectorId} is unavailable, stop it`);
96 if (!this.chargingStation
?.ocppRequestService
) {
97 logger
.info(`${this.logPrefix(connectorId)} Transaction loop waiting for charging station service to be initialized`);
99 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
100 } while (!this.chargingStation
?.ocppRequestService
);
102 const wait
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDelayBetweenTwoTransactions
,
103 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDelayBetweenTwoTransactions
) * 1000;
104 logger
.info(this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
));
105 await Utils
.sleep(wait
);
106 const start
= Utils
.secureRandom();
107 if (start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
) {
108 skippedTransactions
= 0;
110 const startResponse
= await this.startTransaction(connectorId
);
111 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
112 logger
.warn(this.logPrefix(connectorId
) + ' transaction rejected');
113 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_WAIT_TIME
);
115 // Wait until end of transaction
116 const waitTrxEnd
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
117 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
) * 1000;
118 logger
.info(this.logPrefix(connectorId
) + ' transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString() + ' will stop in ' + Utils
.formatDurationMilliSeconds(waitTrxEnd
));
119 await Utils
.sleep(waitTrxEnd
);
121 logger
.info(this.logPrefix(connectorId
) + ' stop transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString());
122 await this.stopTransaction(connectorId
);
125 skippedTransactions
++;
126 skippedTransactionsTotal
++;
127 logger
.info(this.logPrefix(connectorId
) + ' skipped transaction ' + skippedTransactions
.toString() + '/' + skippedTransactionsTotal
.toString());
129 this.lastRunDate
= new Date();
131 await this.stopTransaction(connectorId
);
132 logger
.info(this.logPrefix(connectorId
) + ' stopped on connector');
135 private stopConnector(connectorId
: number): void {
136 this.connectorsStartStatus
[connectorId
] = false;
139 private async startTransaction(connectorId
: number): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
140 const measureId
= 'StartTransaction with ATG';
141 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
142 let startResponse
: StartTransactionResponse
;
143 if (this.chargingStation
.hasAuthorizedTags()) {
144 const idTag
= this.chargingStation
.getRandomIdTag();
145 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
147 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(connectorId
, idTag
);
148 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
149 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
151 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
152 PerformanceStatistics
.endMeasure(measureId
, beginId
);
153 return startResponse
;
155 PerformanceStatistics
.endMeasure(measureId
, beginId
);
156 return authorizeResponse
;
158 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
160 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
161 PerformanceStatistics
.endMeasure(measureId
, beginId
);
162 return startResponse
;
164 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
165 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
166 PerformanceStatistics
.endMeasure(measureId
, beginId
);
167 return startResponse
;
170 private async stopTransaction(connectorId
: number, reason
: StopTransactionReason
= StopTransactionReason
.NONE
): Promise
<StopTransactionResponse
> {
171 const measureId
= 'StopTransaction with ATG';
172 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
173 let transactionId
= 0;
174 let stopResponse
: StopTransactionResponse
;
175 if (this.chargingStation
.getConnector(connectorId
)?.transactionStarted
) {
176 transactionId
= this.chargingStation
.getConnector(connectorId
).transactionId
;
177 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(transactionId
,
178 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
179 this.chargingStation
.getTransactionIdTag(transactionId
),
182 logger
.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
184 PerformanceStatistics
.endMeasure(measureId
, beginId
);
188 private logPrefix(connectorId
?: number): string {
190 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG on connector #' + connectorId
.toString() + ':');
192 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');