1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
6 StartTransactionResponse
,
8 StopTransactionResponse
,
9 } from
'../types/ocpp/Transaction';
11 import type ChargingStation from
'./ChargingStation';
12 import Constants from
'../utils/Constants';
13 import PerformanceStatistics from
'../performance/PerformanceStatistics';
14 import { Status
} from
'../types/AutomaticTransactionGenerator';
15 import Utils from
'../utils/Utils';
16 import logger from
'../utils/Logger';
18 export default class AutomaticTransactionGenerator
{
19 private static readonly instances
: Map
<string, AutomaticTransactionGenerator
> = new Map
<
21 AutomaticTransactionGenerator
24 public started
: boolean;
25 private readonly chargingStation
: ChargingStation
;
26 private readonly connectorsStatus
: Map
<number, Status
>;
28 private constructor(chargingStation
: ChargingStation
) {
29 this.chargingStation
= chargingStation
;
30 this.connectorsStatus
= new Map
<number, Status
>();
31 this.stopConnectors();
35 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
36 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.id
)) {
37 AutomaticTransactionGenerator
.instances
.set(
39 new AutomaticTransactionGenerator(chargingStation
)
42 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.id
);
45 public start(): void {
47 logger
.error(`${this.logPrefix()} trying to start while already started`);
50 this.startConnectors();
56 logger
.error(`${this.logPrefix()} trying to stop while not started`);
59 this.stopConnectors();
63 private startConnectors(): void {
65 this.connectorsStatus
?.size
> 0 &&
66 this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()
68 this.connectorsStatus
.clear();
70 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
71 if (connectorId
> 0) {
72 this.startConnector(connectorId
);
77 private stopConnectors(): void {
78 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
79 if (connectorId
> 0) {
80 this.stopConnector(connectorId
);
85 private async internalStartConnector(connectorId
: number): Promise
<void> {
86 this.initStartConnectorStatus(connectorId
);
88 this.logPrefix(connectorId
) +
89 ' started on connector and will run for ' +
90 Utils
.formatDurationMilliSeconds(
91 this.connectorsStatus
.get(connectorId
).stopDate
.getTime() -
92 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
95 while (this.connectorsStatus
.get(connectorId
).start
) {
96 if (new Date() > this.connectorsStatus
.get(connectorId
).stopDate
) {
97 this.stopConnector(connectorId
);
100 if (!this.chargingStation
.isInAcceptedState()) {
102 this.logPrefix(connectorId
) +
103 ' entered in transaction loop while the charging station is not in accepted state'
105 this.stopConnector(connectorId
);
108 if (!this.chargingStation
.isChargingStationAvailable()) {
110 this.logPrefix(connectorId
) +
111 ' entered in transaction loop while the charging station is unavailable'
113 this.stopConnector(connectorId
);
116 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
120 )} entered in transaction loop while the connector ${connectorId} is unavailable`
122 this.stopConnector(connectorId
);
125 if (!this.chargingStation
?.ocppRequestService
) {
129 )} transaction loop waiting for charging station service to be initialized`
132 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
133 } while (!this.chargingStation
?.ocppRequestService
);
136 Utils
.getRandomInteger(
137 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
138 .maxDelayBetweenTwoTransactions
,
139 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
140 .minDelayBetweenTwoTransactions
143 this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
)
145 await Utils
.sleep(wait
);
146 const start
= Utils
.secureRandom();
148 start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
150 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
152 const startResponse
= await this.startTransaction(connectorId
);
153 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
154 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
155 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
156 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
158 // Wait until end of transaction
160 Utils
.getRandomInteger(
161 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
162 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
165 this.logPrefix(connectorId
) +
167 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() +
168 ' started and will stop in ' +
169 Utils
.formatDurationMilliSeconds(waitTrxEnd
)
171 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
172 await Utils
.sleep(waitTrxEnd
);
175 this.logPrefix(connectorId
) +
176 ' stop transaction ' +
177 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString()
179 await this.stopTransaction(connectorId
);
182 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
183 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
185 this.logPrefix(connectorId
) +
186 ' skipped consecutively ' +
187 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() +
189 this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() +
193 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
195 await this.stopTransaction(connectorId
);
196 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
198 this.logPrefix(connectorId
) +
199 ' stopped on connector and lasted for ' +
200 Utils
.formatDurationMilliSeconds(
201 this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() -
202 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
206 `${this.logPrefix(connectorId)} connector status %j`,
207 this.connectorsStatus
.get(connectorId
)
211 private startConnector(connectorId
: number): void {
212 // Avoid hogging the event loop with a busy loop
214 this.internalStartConnector(connectorId
).catch(() => {
215 /* This is intentional */
220 private stopConnector(connectorId
: number): void {
221 this.connectorsStatus
.set(connectorId
, {
222 ...this.connectorsStatus
.get(connectorId
),
227 private initStartConnectorStatus(connectorId
: number): void {
228 this.connectorsStatus
.get(connectorId
).authorizeRequests
=
229 this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
230 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
=
231 this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
232 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
=
233 this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
234 this.connectorsStatus
.get(connectorId
).startTransactionRequests
=
235 this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
236 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
=
237 this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
238 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
=
239 this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
240 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
=
241 this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
242 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
243 this.connectorsStatus
.get(connectorId
).skippedTransactions
=
244 this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
245 const previousRunDuration
=
246 this?.connectorsStatus
.get(connectorId
)?.startDate
&&
247 this?.connectorsStatus
.get(connectorId
)?.lastRunDate
248 ? this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() -
249 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
251 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
252 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(
253 this.connectorsStatus
.get(connectorId
).startDate
.getTime() +
254 (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
??
255 Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) *
260 this.connectorsStatus
.get(connectorId
).start
= true;
263 private async startTransaction(
265 ): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
266 const measureId
= 'StartTransaction with ATG';
267 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
268 let startResponse
: StartTransactionResponse
;
269 if (this.chargingStation
.hasAuthorizedTags()) {
270 const idTag
= this.chargingStation
.getRandomIdTag();
271 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
273 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(
277 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
278 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
279 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
280 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
282 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(
286 PerformanceStatistics
.endMeasure(measureId
, beginId
);
287 return startResponse
;
289 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
290 PerformanceStatistics
.endMeasure(measureId
, beginId
);
291 return authorizeResponse
;
293 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
295 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(
299 PerformanceStatistics
.endMeasure(measureId
, beginId
);
300 return startResponse
;
302 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
303 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
304 PerformanceStatistics
.endMeasure(measureId
, beginId
);
305 return startResponse
;
308 private async stopTransaction(
310 reason
: StopTransactionReason
= StopTransactionReason
.NONE
311 ): Promise
<StopTransactionResponse
> {
312 const measureId
= 'StopTransaction with ATG';
313 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
314 let transactionId
= 0;
315 let stopResponse
: StopTransactionResponse
;
316 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
317 transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
318 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(
320 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
321 this.chargingStation
.getTransactionIdTag(transactionId
),
324 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
327 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
328 transactionId ? ' ' + transactionId.toString() : ''
332 PerformanceStatistics
.endMeasure(measureId
, beginId
);
336 private logPrefix(connectorId
?: number): string {
338 return Utils
.logPrefix(
340 this.chargingStation
.stationInfo
.chargingStationId
+
341 ' | ATG on connector #' +
342 connectorId
.toString() +
346 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');