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 { RequestCommand
} from
'../types/ocpp/Requests';
15 import { Status
} from
'../types/AutomaticTransactionGenerator';
16 import Utils from
'../utils/Utils';
17 import logger from
'../utils/Logger';
19 export default class AutomaticTransactionGenerator
{
20 private static readonly instances
: Map
<string, AutomaticTransactionGenerator
> = new Map
<
22 AutomaticTransactionGenerator
25 public started
: boolean;
26 private readonly chargingStation
: ChargingStation
;
27 private readonly connectorsStatus
: Map
<number, Status
>;
29 private constructor(chargingStation
: ChargingStation
) {
30 this.chargingStation
= chargingStation
;
31 this.connectorsStatus
= new Map
<number, Status
>();
32 this.stopConnectors();
36 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
37 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.id
)) {
38 AutomaticTransactionGenerator
.instances
.set(
40 new AutomaticTransactionGenerator(chargingStation
)
43 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.id
);
46 public start(): void {
48 logger
.error(`${this.logPrefix()} trying to start while already started`);
51 this.startConnectors();
57 logger
.error(`${this.logPrefix()} trying to stop while not started`);
60 this.stopConnectors();
64 private startConnectors(): void {
66 this.connectorsStatus
?.size
> 0 &&
67 this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()
69 this.connectorsStatus
.clear();
71 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
72 if (connectorId
> 0) {
73 this.startConnector(connectorId
);
78 private stopConnectors(): void {
79 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
80 if (connectorId
> 0) {
81 this.stopConnector(connectorId
);
86 private async internalStartConnector(connectorId
: number): Promise
<void> {
87 this.initStartConnectorStatus(connectorId
);
89 this.logPrefix(connectorId
) +
90 ' started on connector and will run for ' +
91 Utils
.formatDurationMilliSeconds(
92 this.connectorsStatus
.get(connectorId
).stopDate
.getTime() -
93 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
96 while (this.connectorsStatus
.get(connectorId
).start
) {
97 if (new Date() > this.connectorsStatus
.get(connectorId
).stopDate
) {
98 this.stopConnector(connectorId
);
101 if (!this.chargingStation
.isInAcceptedState()) {
103 this.logPrefix(connectorId
) +
104 ' entered in transaction loop while the charging station is not in accepted state'
106 this.stopConnector(connectorId
);
109 if (!this.chargingStation
.isChargingStationAvailable()) {
111 this.logPrefix(connectorId
) +
112 ' entered in transaction loop while the charging station is unavailable'
114 this.stopConnector(connectorId
);
117 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
121 )} entered in transaction loop while the connector ${connectorId} is unavailable`
123 this.stopConnector(connectorId
);
126 if (!this.chargingStation
?.ocppRequestService
) {
130 )} transaction loop waiting for charging station service to be initialized`
133 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
134 } while (!this.chargingStation
?.ocppRequestService
);
137 Utils
.getRandomInteger(
138 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
139 .maxDelayBetweenTwoTransactions
,
140 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
141 .minDelayBetweenTwoTransactions
144 this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
)
146 await Utils
.sleep(wait
);
147 const start
= Utils
.secureRandom();
149 start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
151 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
153 const startResponse
= await this.startTransaction(connectorId
);
154 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
155 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
156 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
157 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
159 // Wait until end of transaction
161 Utils
.getRandomInteger(
162 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
163 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
166 this.logPrefix(connectorId
) +
168 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() +
169 ' started and will stop in ' +
170 Utils
.formatDurationMilliSeconds(waitTrxEnd
)
172 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
173 await Utils
.sleep(waitTrxEnd
);
176 this.logPrefix(connectorId
) +
177 ' stop transaction ' +
178 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString()
180 await this.stopTransaction(connectorId
);
183 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
184 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
186 this.logPrefix(connectorId
) +
187 ' skipped consecutively ' +
188 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() +
190 this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() +
194 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
196 await this.stopTransaction(connectorId
);
197 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
199 this.logPrefix(connectorId
) +
200 ' stopped on connector and lasted for ' +
201 Utils
.formatDurationMilliSeconds(
202 this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() -
203 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
207 `${this.logPrefix(connectorId)} connector status %j`,
208 this.connectorsStatus
.get(connectorId
)
212 private startConnector(connectorId
: number): void {
213 // Avoid hogging the event loop with a busy loop
215 this.internalStartConnector(connectorId
).catch(() => {
216 /* This is intentional */
221 private stopConnector(connectorId
: number): void {
222 this.connectorsStatus
.set(connectorId
, {
223 ...this.connectorsStatus
.get(connectorId
),
228 private initStartConnectorStatus(connectorId
: number): void {
229 this.connectorsStatus
.get(connectorId
).authorizeRequests
=
230 this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
231 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
=
232 this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
233 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
=
234 this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
235 this.connectorsStatus
.get(connectorId
).startTransactionRequests
=
236 this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
237 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
=
238 this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
239 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
=
240 this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
241 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
=
242 this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
243 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
244 this.connectorsStatus
.get(connectorId
).skippedTransactions
=
245 this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
246 const previousRunDuration
=
247 this?.connectorsStatus
.get(connectorId
)?.startDate
&&
248 this?.connectorsStatus
.get(connectorId
)?.lastRunDate
249 ? this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() -
250 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
252 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
253 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(
254 this.connectorsStatus
.get(connectorId
).startDate
.getTime() +
255 (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
??
256 Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) *
261 this.connectorsStatus
.get(connectorId
).start
= true;
264 private async startTransaction(
266 ): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
267 const measureId
= 'StartTransaction with ATG';
268 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
269 let startResponse
: StartTransactionResponse
;
270 if (this.chargingStation
.hasAuthorizedTags()) {
271 const idTag
= this.chargingStation
.getRandomIdTag();
272 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
273 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
= idTag
;
275 const authorizeResponse
: AuthorizeResponse
=
276 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
277 RequestCommand
.AUTHORIZE
,
281 )) as AuthorizeResponse
;
282 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
283 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
284 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
285 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
287 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(
291 PerformanceStatistics
.endMeasure(measureId
, beginId
);
292 return startResponse
;
294 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
295 PerformanceStatistics
.endMeasure(measureId
, beginId
);
296 return authorizeResponse
;
298 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
300 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(
304 PerformanceStatistics
.endMeasure(measureId
, beginId
);
305 return startResponse
;
307 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
308 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
309 PerformanceStatistics
.endMeasure(measureId
, beginId
);
310 return startResponse
;
313 private async stopTransaction(
315 reason
: StopTransactionReason
= StopTransactionReason
.NONE
316 ): Promise
<StopTransactionResponse
> {
317 const measureId
= 'StopTransaction with ATG';
318 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
319 let transactionId
= 0;
320 let stopResponse
: StopTransactionResponse
;
321 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
322 transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
323 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(
325 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
326 this.chargingStation
.getTransactionIdTag(transactionId
),
329 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
332 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
333 transactionId ? ' ' + transactionId.toString() : ''
337 PerformanceStatistics
.endMeasure(measureId
, beginId
);
341 private logPrefix(connectorId
?: number): string {
343 return Utils
.logPrefix(
345 this.chargingStation
.stationInfo
.chargingStationId
+
346 ' | ATG on connector #' +
347 connectorId
.toString() +
351 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');