1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
7 StartTransactionRequest
,
8 StartTransactionResponse
,
10 StopTransactionRequest
,
11 StopTransactionResponse
,
12 } from
'../types/ocpp/Transaction';
13 import { MeterValuesRequest
, RequestCommand
} from
'../types/ocpp/Requests';
15 import type ChargingStation from
'./ChargingStation';
16 import Constants from
'../utils/Constants';
17 import { MeterValuesResponse
} from
'../types/ocpp/Responses';
18 import { OCPP16ServiceUtils
} from
'./ocpp/1.6/OCPP16ServiceUtils';
19 import PerformanceStatistics from
'../performance/PerformanceStatistics';
20 import { Status
} from
'../types/AutomaticTransactionGenerator';
21 import Utils from
'../utils/Utils';
22 import logger from
'../utils/Logger';
24 export default class AutomaticTransactionGenerator
{
25 private static readonly instances
: Map
<string, AutomaticTransactionGenerator
> = new Map
<
27 AutomaticTransactionGenerator
30 public started
: boolean;
31 private readonly chargingStation
: ChargingStation
;
32 private readonly connectorsStatus
: Map
<number, Status
>;
34 private constructor(chargingStation
: ChargingStation
) {
35 this.chargingStation
= chargingStation
;
36 this.connectorsStatus
= new Map
<number, Status
>();
37 this.stopConnectors();
41 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
42 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.hashId
)) {
43 AutomaticTransactionGenerator
.instances
.set(
44 chargingStation
.hashId
,
45 new AutomaticTransactionGenerator(chargingStation
)
48 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.hashId
);
51 public start(): void {
53 logger
.error(`${this.logPrefix()} trying to start while already started`);
56 this.startConnectors();
62 logger
.error(`${this.logPrefix()} trying to stop while not started`);
65 this.stopConnectors();
69 private startConnectors(): void {
71 this.connectorsStatus
?.size
> 0 &&
72 this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()
74 this.connectorsStatus
.clear();
76 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
77 if (connectorId
> 0) {
78 this.startConnector(connectorId
);
83 private stopConnectors(): void {
84 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
85 if (connectorId
> 0) {
86 this.stopConnector(connectorId
);
91 private async internalStartConnector(connectorId
: number): Promise
<void> {
92 this.initStartConnectorStatus(connectorId
);
94 this.logPrefix(connectorId
) +
95 ' started on connector and will run for ' +
96 Utils
.formatDurationMilliSeconds(
97 this.connectorsStatus
.get(connectorId
).stopDate
.getTime() -
98 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
101 while (this.connectorsStatus
.get(connectorId
).start
) {
102 if (new Date() > this.connectorsStatus
.get(connectorId
).stopDate
) {
103 this.stopConnector(connectorId
);
106 if (!this.chargingStation
.isInAcceptedState()) {
108 this.logPrefix(connectorId
) +
109 ' entered in transaction loop while the charging station is not in accepted state'
111 this.stopConnector(connectorId
);
114 if (!this.chargingStation
.isChargingStationAvailable()) {
116 this.logPrefix(connectorId
) +
117 ' entered in transaction loop while the charging station is unavailable'
119 this.stopConnector(connectorId
);
122 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
126 )} entered in transaction loop while the connector ${connectorId} is unavailable`
128 this.stopConnector(connectorId
);
131 if (!this.chargingStation
?.ocppRequestService
) {
135 )} transaction loop waiting for charging station service to be initialized`
138 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
139 } while (!this.chargingStation
?.ocppRequestService
);
142 Utils
.getRandomInteger(
143 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
144 .maxDelayBetweenTwoTransactions
,
145 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
146 .minDelayBetweenTwoTransactions
149 this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
)
151 await Utils
.sleep(wait
);
152 const start
= Utils
.secureRandom();
154 start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
156 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
158 const startResponse
= await this.startTransaction(connectorId
);
159 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
160 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
161 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
162 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
164 // Wait until end of transaction
166 Utils
.getRandomInteger(
167 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
168 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
171 this.logPrefix(connectorId
) +
173 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() +
174 ' started and will stop in ' +
175 Utils
.formatDurationMilliSeconds(waitTrxEnd
)
177 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
178 await Utils
.sleep(waitTrxEnd
);
181 this.logPrefix(connectorId
) +
182 ' stop transaction ' +
183 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString()
185 await this.stopTransaction(connectorId
);
188 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
189 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
191 this.logPrefix(connectorId
) +
192 ' skipped consecutively ' +
193 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() +
195 this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() +
199 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
201 await this.stopTransaction(connectorId
);
202 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
204 this.logPrefix(connectorId
) +
205 ' stopped on connector and lasted for ' +
206 Utils
.formatDurationMilliSeconds(
207 this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() -
208 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
212 `${this.logPrefix(connectorId)} connector status %j`,
213 this.connectorsStatus
.get(connectorId
)
217 private startConnector(connectorId
: number): void {
218 // Avoid hogging the event loop with a busy loop
220 this.internalStartConnector(connectorId
).catch(() => {
221 /* This is intentional */
226 private stopConnector(connectorId
: number): void {
227 this.connectorsStatus
.set(connectorId
, {
228 ...this.connectorsStatus
.get(connectorId
),
233 private initStartConnectorStatus(connectorId
: number): void {
234 this.connectorsStatus
.get(connectorId
).authorizeRequests
=
235 this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
236 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
=
237 this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
238 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
=
239 this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
240 this.connectorsStatus
.get(connectorId
).startTransactionRequests
=
241 this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
242 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
=
243 this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
244 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
=
245 this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
246 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
=
247 this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
248 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
249 this.connectorsStatus
.get(connectorId
).skippedTransactions
=
250 this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
251 const previousRunDuration
=
252 this?.connectorsStatus
.get(connectorId
)?.startDate
&&
253 this?.connectorsStatus
.get(connectorId
)?.lastRunDate
254 ? this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() -
255 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
257 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
258 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(
259 this.connectorsStatus
.get(connectorId
).startDate
.getTime() +
260 (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
??
261 Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) *
266 this.connectorsStatus
.get(connectorId
).start
= true;
269 private async startTransaction(
271 ): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
272 const measureId
= 'StartTransaction with ATG';
273 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
274 let startResponse
: StartTransactionResponse
;
275 if (this.chargingStation
.hasAuthorizedTags()) {
276 const idTag
= this.chargingStation
.getRandomIdTag();
277 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
278 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
= idTag
;
280 const authorizeResponse
: AuthorizeResponse
=
281 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
284 >(RequestCommand
.AUTHORIZE
, {
287 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
288 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
289 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
290 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
292 startResponse
= await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
293 StartTransactionRequest
,
294 StartTransactionResponse
295 >(RequestCommand
.START_TRANSACTION
, {
299 PerformanceStatistics
.endMeasure(measureId
, beginId
);
300 return startResponse
;
302 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
303 PerformanceStatistics
.endMeasure(measureId
, beginId
);
304 return authorizeResponse
;
306 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
308 startResponse
= await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
309 StartTransactionRequest
,
310 StartTransactionResponse
311 >(RequestCommand
.START_TRANSACTION
, {
315 PerformanceStatistics
.endMeasure(measureId
, beginId
);
316 return startResponse
;
318 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
319 startResponse
= await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
320 StartTransactionRequest
,
321 StartTransactionResponse
322 >(RequestCommand
.START_TRANSACTION
, { connectorId
});
323 PerformanceStatistics
.endMeasure(measureId
, beginId
);
324 return startResponse
;
327 private async stopTransaction(
329 reason
: StopTransactionReason
= StopTransactionReason
.NONE
330 ): Promise
<StopTransactionResponse
> {
331 const measureId
= 'StopTransaction with ATG';
332 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
333 let transactionId
= 0;
334 let stopResponse
: StopTransactionResponse
;
335 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
336 transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
338 this.chargingStation
.getBeginEndMeterValues() &&
339 this.chargingStation
.getOcppStrictCompliance() &&
340 !this.chargingStation
.getOutOfOrderEndMeterValues()
342 // FIXME: Implement OCPP version agnostic helpers
343 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
344 this.chargingStation
,
346 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
348 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
351 >(RequestCommand
.METER_VALUES
, {
354 meterValue
: transactionEndMeterValue
,
357 stopResponse
= await this.chargingStation
.ocppRequestService
.sendMessageHandler
<
358 StopTransactionRequest
,
359 StopTransactionResponse
360 >(RequestCommand
.STOP_TRANSACTION
, {
362 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
363 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
366 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
369 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
370 transactionId ? ' ' + transactionId.toString() : ''
374 PerformanceStatistics
.endMeasure(measureId
, beginId
);
378 private logPrefix(connectorId
?: number): string {
380 return Utils
.logPrefix(
382 this.chargingStation
.stationInfo
.chargingStationId
+
383 ' | ATG on connector #' +
384 connectorId
.toString() +
388 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');