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`);
94 this.stopConnector(connectorId
);
97 if (!this.chargingStation
?.ocppRequestService
) {
98 logger
.info(`${this.logPrefix(connectorId)} Transaction loop waiting for charging station service to be initialized`);
100 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
101 } while (!this.chargingStation
?.ocppRequestService
);
103 const wait
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDelayBetweenTwoTransactions
,
104 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDelayBetweenTwoTransactions
) * 1000;
105 logger
.info(this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
));
106 await Utils
.sleep(wait
);
107 const start
= Utils
.secureRandom();
108 if (start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
) {
109 skippedTransactions
= 0;
111 const startResponse
= await this.startTransaction(connectorId
);
112 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
113 logger
.warn(this.logPrefix(connectorId
) + ' transaction rejected');
114 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_WAIT_TIME
);
116 // Wait until end of transaction
117 const waitTrxEnd
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
118 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
) * 1000;
119 logger
.info(this.logPrefix(connectorId
) + ' transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString() + ' will stop in ' + Utils
.formatDurationMilliSeconds(waitTrxEnd
));
120 await Utils
.sleep(waitTrxEnd
);
122 logger
.info(this.logPrefix(connectorId
) + ' stop transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString());
123 await this.stopTransaction(connectorId
);
126 skippedTransactions
++;
127 skippedTransactionsTotal
++;
128 logger
.info(this.logPrefix(connectorId
) + ' skipped transaction ' + skippedTransactions
.toString() + '/' + skippedTransactionsTotal
.toString());
130 this.lastRunDate
= new Date();
132 await this.stopTransaction(connectorId
);
133 logger
.info(this.logPrefix(connectorId
) + ' stopped on connector');
136 private stopConnector(connectorId
: number): void {
137 this.connectorsStartStatus
[connectorId
] = false;
140 private async startTransaction(connectorId
: number): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
141 const measureId
= 'StartTransaction with ATG';
142 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
143 let startResponse
: StartTransactionResponse
;
144 if (this.chargingStation
.hasAuthorizedTags()) {
145 const idTag
= this.chargingStation
.getRandomIdTag();
146 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
148 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(connectorId
, idTag
);
149 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
150 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
152 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
153 PerformanceStatistics
.endMeasure(measureId
, beginId
);
154 return startResponse
;
156 PerformanceStatistics
.endMeasure(measureId
, beginId
);
157 return authorizeResponse
;
159 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
161 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
162 PerformanceStatistics
.endMeasure(measureId
, beginId
);
163 return startResponse
;
165 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
166 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
167 PerformanceStatistics
.endMeasure(measureId
, beginId
);
168 return startResponse
;
171 private async stopTransaction(connectorId
: number, reason
: StopTransactionReason
= StopTransactionReason
.NONE
): Promise
<StopTransactionResponse
> {
172 const measureId
= 'StopTransaction with ATG';
173 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
174 let transactionId
= 0;
175 let stopResponse
: StopTransactionResponse
;
176 if (this.chargingStation
.getConnector(connectorId
)?.transactionStarted
) {
177 transactionId
= this.chargingStation
.getConnector(connectorId
).transactionId
;
178 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(transactionId
,
179 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
180 this.chargingStation
.getTransactionIdTag(transactionId
),
183 logger
.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
185 PerformanceStatistics
.endMeasure(measureId
, beginId
);
189 private logPrefix(connectorId
?: number): string {
191 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG on connector #' + connectorId
.toString() + ':');
193 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');