1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import { AuthorizationStatus
, AuthorizeResponse
, StartTransactionResponse
, StopTransactionReason
, StopTransactionResponse
} from
'../types/ocpp/Transaction';
5 import type ChargingStation from
'./ChargingStation';
6 import Constants from
'../utils/Constants';
7 import PerformanceStatistics from
'../performance/PerformanceStatistics';
8 import { Status
} from
'../types/AutomaticTransactionGenerator';
9 import Utils from
'../utils/Utils';
10 import logger from
'../utils/Logger';
12 export default class AutomaticTransactionGenerator
{
13 private static readonly instances
: Map
<string, AutomaticTransactionGenerator
> = new Map
<string, AutomaticTransactionGenerator
>();
14 public started
: boolean;
15 private readonly chargingStation
: ChargingStation
;
16 private readonly connectorsStatus
: Map
<number, Status
>;
18 private constructor(chargingStation
: ChargingStation
) {
19 this.chargingStation
= chargingStation
;
20 this.connectorsStatus
= new Map
<number, Status
>();
21 this.stopConnectors();
25 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
26 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.id
)) {
27 AutomaticTransactionGenerator
.instances
.set(chargingStation
.id
, new AutomaticTransactionGenerator(chargingStation
));
29 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.id
);
32 public start(): void {
34 logger
.error(`${this.logPrefix()} trying to start while already started`);
37 this.startConnectors();
43 logger
.error(`${this.logPrefix()} trying to stop while not started`);
46 this.stopConnectors();
50 private startConnectors(): void {
51 if (this.connectorsStatus
?.size
> 0 && this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()) {
52 this.connectorsStatus
.clear();
54 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
55 if (connectorId
> 0) {
56 this.startConnector(connectorId
);
61 private stopConnectors(): void {
62 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
63 if (connectorId
> 0) {
64 this.stopConnector(connectorId
);
69 private async internalStartConnector(connectorId
: number): Promise
<void> {
70 this.initStartConnectorStatus(connectorId
);
71 logger
.info(this.logPrefix(connectorId
) + ' started on connector and will run for ' + Utils
.formatDurationMilliSeconds(this.connectorsStatus
.get(connectorId
).stopDate
.getTime() - this.connectorsStatus
.get(connectorId
).startDate
.getTime()));
72 while (this.connectorsStatus
.get(connectorId
).start
) {
73 if ((new Date()) > this.connectorsStatus
.get(connectorId
).stopDate
) {
74 this.stopConnector(connectorId
);
77 if (!this.chargingStation
.isInAcceptedState()) {
78 logger
.error(this.logPrefix(connectorId
) + ' entered in transaction loop while the charging station is not in accepted state');
79 this.stopConnector(connectorId
);
82 if (!this.chargingStation
.isChargingStationAvailable()) {
83 logger
.info(this.logPrefix(connectorId
) + ' entered in transaction loop while the charging station is unavailable');
84 this.stopConnector(connectorId
);
87 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
88 logger
.info(`${this.logPrefix(connectorId)} entered in transaction loop while the connector ${connectorId} is unavailable`);
89 this.stopConnector(connectorId
);
92 if (!this.chargingStation
?.ocppRequestService
) {
93 logger
.info(`${this.logPrefix(connectorId)} transaction loop waiting for charging station service to be initialized`);
95 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
96 } while (!this.chargingStation
?.ocppRequestService
);
98 const wait
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDelayBetweenTwoTransactions
,
99 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDelayBetweenTwoTransactions
) * 1000;
100 logger
.info(this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
));
101 await Utils
.sleep(wait
);
102 const start
= Utils
.secureRandom();
103 if (start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
) {
104 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
106 const startResponse
= await this.startTransaction(connectorId
);
107 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
108 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
109 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
110 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
112 // Wait until end of transaction
113 const waitTrxEnd
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
114 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
) * 1000;
115 logger
.info(this.logPrefix(connectorId
) + ' transaction ' + this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() + ' started and will stop in ' + Utils
.formatDurationMilliSeconds(waitTrxEnd
));
116 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
117 await Utils
.sleep(waitTrxEnd
);
119 logger
.info(this.logPrefix(connectorId
) + ' stop transaction ' + this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString());
120 await this.stopTransaction(connectorId
);
123 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
124 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
125 logger
.info(this.logPrefix(connectorId
) + ' skipped consecutively ' + this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() + '/' + this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() + ' transaction(s)');
127 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
129 await this.stopTransaction(connectorId
);
130 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
131 logger
.info(this.logPrefix(connectorId
) + ' stopped on connector and lasted for ' + Utils
.formatDurationMilliSeconds(this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() - this.connectorsStatus
.get(connectorId
).startDate
.getTime()));
132 logger
.debug(`${this.logPrefix(connectorId)} connector status %j`, this.connectorsStatus
.get(connectorId
));
135 private startConnector(connectorId
: number): void {
136 // Avoid hogging the event loop with a busy loop
138 this.internalStartConnector(connectorId
).catch(() => { /* This is intentional */ });
142 private stopConnector(connectorId
: number): void {
143 this.connectorsStatus
.set(connectorId
, { ...this.connectorsStatus
.get(connectorId
), start
: false });
146 private initStartConnectorStatus(connectorId
: number): void {
147 this.connectorsStatus
.get(connectorId
).authorizeRequests
= this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
148 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
= this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
149 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
= this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
150 this.connectorsStatus
.get(connectorId
).startTransactionRequests
= this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
151 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
= this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
152 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
= this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
153 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
= this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
154 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
155 this.connectorsStatus
.get(connectorId
).skippedTransactions
= this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
156 const previousRunDuration
= (this?.connectorsStatus
.get(connectorId
)?.startDate
&& this?.connectorsStatus
.get(connectorId
)?.lastRunDate
)
157 ? (this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() - this.connectorsStatus
.get(connectorId
).startDate
.getTime())
159 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
160 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(this.connectorsStatus
.get(connectorId
).startDate
.getTime()
161 + (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
?? Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) * 3600 * 1000
162 - previousRunDuration
);
163 this.connectorsStatus
.get(connectorId
).start
= true;
166 private async startTransaction(connectorId
: number): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
167 const measureId
= 'StartTransaction with ATG';
168 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
169 let startResponse
: StartTransactionResponse
;
170 if (this.chargingStation
.hasAuthorizedTags()) {
171 const idTag
= this.chargingStation
.getRandomIdTag();
172 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
174 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(connectorId
, idTag
);
175 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
176 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
177 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
178 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
180 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
181 PerformanceStatistics
.endMeasure(measureId
, beginId
);
182 return startResponse
;
184 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
185 PerformanceStatistics
.endMeasure(measureId
, beginId
);
186 return authorizeResponse
;
188 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
190 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
191 PerformanceStatistics
.endMeasure(measureId
, beginId
);
192 return startResponse
;
194 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
195 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
196 PerformanceStatistics
.endMeasure(measureId
, beginId
);
197 return startResponse
;
200 private async stopTransaction(connectorId
: number, reason
: StopTransactionReason
= StopTransactionReason
.NONE
): Promise
<StopTransactionResponse
> {
201 const measureId
= 'StopTransaction with ATG';
202 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
203 let transactionId
= 0;
204 let stopResponse
: StopTransactionResponse
;
205 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
206 transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
207 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(transactionId
,
208 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
209 this.chargingStation
.getTransactionIdTag(transactionId
),
211 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
213 logger
.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
215 PerformanceStatistics
.endMeasure(measureId
, beginId
);
219 private logPrefix(connectorId
?: number): string {
221 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG on connector #' + connectorId
.toString() + ':');
223 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');