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
23 public started
: boolean;
24 private readonly chargingStation
: ChargingStation
;
25 private readonly connectorsStatus
: Map
<number, Status
>;
27 private constructor(chargingStation
: ChargingStation
) {
28 this.chargingStation
= chargingStation
;
29 this.connectorsStatus
= new Map
<number, Status
>();
30 this.stopConnectors();
34 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
35 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.id
)) {
36 AutomaticTransactionGenerator
.instances
.set(
38 new AutomaticTransactionGenerator(chargingStation
)
41 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.id
);
44 public start(): void {
46 logger
.error(`${this.logPrefix()} trying to start while already started`);
49 this.startConnectors();
55 logger
.error(`${this.logPrefix()} trying to stop while not started`);
58 this.stopConnectors();
62 private startConnectors(): void {
64 this.connectorsStatus
?.size
> 0 &&
65 this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()
67 this.connectorsStatus
.clear();
69 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
70 if (connectorId
> 0) {
71 this.startConnector(connectorId
);
76 private stopConnectors(): void {
77 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
78 if (connectorId
> 0) {
79 this.stopConnector(connectorId
);
84 private async internalStartConnector(connectorId
: number): Promise
<void> {
85 this.initStartConnectorStatus(connectorId
);
87 this.logPrefix(connectorId
) +
88 ' started on connector and will run for ' +
89 Utils
.formatDurationMilliSeconds(
90 this.connectorsStatus
.get(connectorId
).stopDate
.getTime() -
91 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
94 while (this.connectorsStatus
.get(connectorId
).start
) {
95 if (new Date() > this.connectorsStatus
.get(connectorId
).stopDate
) {
96 this.stopConnector(connectorId
);
99 if (!this.chargingStation
.isInAcceptedState()) {
101 this.logPrefix(connectorId
) +
102 ' entered in transaction loop while the charging station is not in accepted state'
104 this.stopConnector(connectorId
);
107 if (!this.chargingStation
.isChargingStationAvailable()) {
109 this.logPrefix(connectorId
) +
110 ' entered in transaction loop while the charging station is unavailable'
112 this.stopConnector(connectorId
);
115 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
119 )} entered in transaction loop while the connector ${connectorId} is unavailable`
121 this.stopConnector(connectorId
);
124 if (!this.chargingStation
?.ocppRequestService
) {
128 )} transaction loop waiting for charging station service to be initialized`
131 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
132 } while (!this.chargingStation
?.ocppRequestService
);
135 Utils
.getRandomInteger(
136 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
137 .maxDelayBetweenTwoTransactions
,
138 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
139 .minDelayBetweenTwoTransactions
142 this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
)
144 await Utils
.sleep(wait
);
145 const start
= Utils
.secureRandom();
147 start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
149 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
151 const startResponse
= await this.startTransaction(connectorId
);
152 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
153 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
154 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
155 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
157 // Wait until end of transaction
159 Utils
.getRandomInteger(
160 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
161 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
164 this.logPrefix(connectorId
) +
166 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() +
167 ' started and will stop in ' +
168 Utils
.formatDurationMilliSeconds(waitTrxEnd
)
170 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
171 await Utils
.sleep(waitTrxEnd
);
174 this.logPrefix(connectorId
) +
175 ' stop transaction ' +
176 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString()
178 await this.stopTransaction(connectorId
);
181 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
182 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
184 this.logPrefix(connectorId
) +
185 ' skipped consecutively ' +
186 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() +
188 this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() +
192 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
194 await this.stopTransaction(connectorId
);
195 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
197 this.logPrefix(connectorId
) +
198 ' stopped on connector and lasted for ' +
199 Utils
.formatDurationMilliSeconds(
200 this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() -
201 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
205 `${this.logPrefix(connectorId)} connector status %j`,
206 this.connectorsStatus
.get(connectorId
)
210 private startConnector(connectorId
: number): void {
211 // Avoid hogging the event loop with a busy loop
213 this.internalStartConnector(connectorId
).catch(() => {
214 /* This is intentional */
219 private stopConnector(connectorId
: number): void {
220 this.connectorsStatus
.set(connectorId
, {
221 ...this.connectorsStatus
.get(connectorId
),
226 private initStartConnectorStatus(connectorId
: number): void {
227 this.connectorsStatus
.get(connectorId
).authorizeRequests
=
228 this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
229 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
=
230 this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
231 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
=
232 this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
233 this.connectorsStatus
.get(connectorId
).startTransactionRequests
=
234 this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
235 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
=
236 this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
237 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
=
238 this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
239 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
=
240 this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
241 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
242 this.connectorsStatus
.get(connectorId
).skippedTransactions
=
243 this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
244 const previousRunDuration
=
245 this?.connectorsStatus
.get(connectorId
)?.startDate
&&
246 this?.connectorsStatus
.get(connectorId
)?.lastRunDate
247 ? this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() -
248 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
250 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
251 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(
252 this.connectorsStatus
.get(connectorId
).startDate
.getTime() +
253 (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
??
254 Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) *
259 this.connectorsStatus
.get(connectorId
).start
= true;
262 private async startTransaction(
264 ): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
265 const measureId
= 'StartTransaction with ATG';
266 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
267 let startResponse
: StartTransactionResponse
;
268 if (this.chargingStation
.hasAuthorizedTags()) {
269 const idTag
= this.chargingStation
.getRandomIdTag();
270 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
272 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(
276 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
277 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
278 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
279 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
281 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(
285 PerformanceStatistics
.endMeasure(measureId
, beginId
);
286 return startResponse
;
288 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
289 PerformanceStatistics
.endMeasure(measureId
, beginId
);
290 return authorizeResponse
;
292 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
294 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(
298 PerformanceStatistics
.endMeasure(measureId
, beginId
);
299 return startResponse
;
301 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
302 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
303 PerformanceStatistics
.endMeasure(measureId
, beginId
);
304 return startResponse
;
307 private async stopTransaction(
309 reason
: StopTransactionReason
= StopTransactionReason
.NONE
310 ): Promise
<StopTransactionResponse
> {
311 const measureId
= 'StopTransaction with ATG';
312 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
313 let transactionId
= 0;
314 let stopResponse
: StopTransactionResponse
;
315 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
316 transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
317 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(
319 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
320 this.chargingStation
.getTransactionIdTag(transactionId
),
323 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
326 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
327 transactionId ? ' ' + transactionId.toString() : ''
331 PerformanceStatistics
.endMeasure(measureId
, beginId
);
335 private logPrefix(connectorId
?: number): string {
337 return Utils
.logPrefix(
339 this.chargingStation
.stationInfo
.chargingStationId
+
340 ' | ATG on connector #' +
341 connectorId
.toString() +
345 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');