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 { OCPP16ServiceUtils
} from
'./ocpp/1.6/OCPP16ServiceUtils';
14 import PerformanceStatistics from
'../performance/PerformanceStatistics';
15 import { RequestCommand
} from
'../types/ocpp/Requests';
16 import { Status
} from
'../types/AutomaticTransactionGenerator';
17 import Utils from
'../utils/Utils';
18 import logger from
'../utils/Logger';
20 export default class AutomaticTransactionGenerator
{
21 private static readonly instances
: Map
<string, AutomaticTransactionGenerator
> = new Map
<
23 AutomaticTransactionGenerator
26 public started
: boolean;
27 private readonly chargingStation
: ChargingStation
;
28 private readonly connectorsStatus
: Map
<number, Status
>;
30 private constructor(chargingStation
: ChargingStation
) {
31 this.chargingStation
= chargingStation
;
32 this.connectorsStatus
= new Map
<number, Status
>();
33 this.stopConnectors();
37 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
38 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.id
)) {
39 AutomaticTransactionGenerator
.instances
.set(
41 new AutomaticTransactionGenerator(chargingStation
)
44 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.id
);
47 public start(): void {
49 logger
.error(`${this.logPrefix()} trying to start while already started`);
52 this.startConnectors();
58 logger
.error(`${this.logPrefix()} trying to stop while not started`);
61 this.stopConnectors();
65 private startConnectors(): void {
67 this.connectorsStatus
?.size
> 0 &&
68 this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()
70 this.connectorsStatus
.clear();
72 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
73 if (connectorId
> 0) {
74 this.startConnector(connectorId
);
79 private stopConnectors(): void {
80 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
81 if (connectorId
> 0) {
82 this.stopConnector(connectorId
);
87 private async internalStartConnector(connectorId
: number): Promise
<void> {
88 this.initStartConnectorStatus(connectorId
);
90 this.logPrefix(connectorId
) +
91 ' started on connector and will run for ' +
92 Utils
.formatDurationMilliSeconds(
93 this.connectorsStatus
.get(connectorId
).stopDate
.getTime() -
94 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
97 while (this.connectorsStatus
.get(connectorId
).start
) {
98 if (new Date() > this.connectorsStatus
.get(connectorId
).stopDate
) {
99 this.stopConnector(connectorId
);
102 if (!this.chargingStation
.isInAcceptedState()) {
104 this.logPrefix(connectorId
) +
105 ' entered in transaction loop while the charging station is not in accepted state'
107 this.stopConnector(connectorId
);
110 if (!this.chargingStation
.isChargingStationAvailable()) {
112 this.logPrefix(connectorId
) +
113 ' entered in transaction loop while the charging station is unavailable'
115 this.stopConnector(connectorId
);
118 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
122 )} entered in transaction loop while the connector ${connectorId} is unavailable`
124 this.stopConnector(connectorId
);
127 if (!this.chargingStation
?.ocppRequestService
) {
131 )} transaction loop waiting for charging station service to be initialized`
134 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
135 } while (!this.chargingStation
?.ocppRequestService
);
138 Utils
.getRandomInteger(
139 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
140 .maxDelayBetweenTwoTransactions
,
141 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
142 .minDelayBetweenTwoTransactions
145 this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
)
147 await Utils
.sleep(wait
);
148 const start
= Utils
.secureRandom();
150 start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
152 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
154 const startResponse
= await this.startTransaction(connectorId
);
155 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
156 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
157 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
158 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
160 // Wait until end of transaction
162 Utils
.getRandomInteger(
163 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
164 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
167 this.logPrefix(connectorId
) +
169 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() +
170 ' started and will stop in ' +
171 Utils
.formatDurationMilliSeconds(waitTrxEnd
)
173 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
174 await Utils
.sleep(waitTrxEnd
);
177 this.logPrefix(connectorId
) +
178 ' stop transaction ' +
179 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString()
181 await this.stopTransaction(connectorId
);
184 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
185 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
187 this.logPrefix(connectorId
) +
188 ' skipped consecutively ' +
189 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() +
191 this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() +
195 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
197 await this.stopTransaction(connectorId
);
198 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
200 this.logPrefix(connectorId
) +
201 ' stopped on connector and lasted for ' +
202 Utils
.formatDurationMilliSeconds(
203 this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() -
204 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
208 `${this.logPrefix(connectorId)} connector status %j`,
209 this.connectorsStatus
.get(connectorId
)
213 private startConnector(connectorId
: number): void {
214 // Avoid hogging the event loop with a busy loop
216 this.internalStartConnector(connectorId
).catch(() => {
217 /* This is intentional */
222 private stopConnector(connectorId
: number): void {
223 this.connectorsStatus
.set(connectorId
, {
224 ...this.connectorsStatus
.get(connectorId
),
229 private initStartConnectorStatus(connectorId
: number): void {
230 this.connectorsStatus
.get(connectorId
).authorizeRequests
=
231 this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
232 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
=
233 this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
234 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
=
235 this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
236 this.connectorsStatus
.get(connectorId
).startTransactionRequests
=
237 this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
238 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
=
239 this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
240 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
=
241 this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
242 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
=
243 this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
244 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
245 this.connectorsStatus
.get(connectorId
).skippedTransactions
=
246 this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
247 const previousRunDuration
=
248 this?.connectorsStatus
.get(connectorId
)?.startDate
&&
249 this?.connectorsStatus
.get(connectorId
)?.lastRunDate
250 ? this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() -
251 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
253 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
254 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(
255 this.connectorsStatus
.get(connectorId
).startDate
.getTime() +
256 (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
??
257 Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) *
262 this.connectorsStatus
.get(connectorId
).start
= true;
265 private async startTransaction(
267 ): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
268 const measureId
= 'StartTransaction with ATG';
269 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
270 let startResponse
: StartTransactionResponse
;
271 if (this.chargingStation
.hasAuthorizedTags()) {
272 const idTag
= this.chargingStation
.getRandomIdTag();
273 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
274 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
= idTag
;
276 const authorizeResponse
: AuthorizeResponse
=
277 (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
278 RequestCommand
.AUTHORIZE
,
282 )) as AuthorizeResponse
;
283 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
284 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
285 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
286 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
288 startResponse
= (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
289 RequestCommand
.START_TRANSACTION
,
294 )) as StartTransactionResponse
;
295 PerformanceStatistics
.endMeasure(measureId
, beginId
);
296 return startResponse
;
298 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
299 PerformanceStatistics
.endMeasure(measureId
, beginId
);
300 return authorizeResponse
;
302 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
304 startResponse
= (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
305 RequestCommand
.START_TRANSACTION
,
310 )) as StartTransactionResponse
;
311 PerformanceStatistics
.endMeasure(measureId
, beginId
);
312 return startResponse
;
314 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
315 startResponse
= (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
316 RequestCommand
.START_TRANSACTION
,
318 )) as StartTransactionResponse
;
319 PerformanceStatistics
.endMeasure(measureId
, beginId
);
320 return startResponse
;
323 private async stopTransaction(
325 reason
: StopTransactionReason
= StopTransactionReason
.NONE
326 ): Promise
<StopTransactionResponse
> {
327 const measureId
= 'StopTransaction with ATG';
328 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
329 let transactionId
= 0;
330 let stopResponse
: StopTransactionResponse
;
331 if (this.chargingStation
.getConnectorStatus(connectorId
)?.transactionStarted
) {
332 transactionId
= this.chargingStation
.getConnectorStatus(connectorId
).transactionId
;
334 this.chargingStation
.getBeginEndMeterValues() &&
335 this.chargingStation
.getOcppStrictCompliance() &&
336 !this.chargingStation
.getOutOfOrderEndMeterValues()
338 // FIXME: Implement OCPP version agnostic helpers
339 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(
340 this.chargingStation
,
342 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
)
344 await this.chargingStation
.ocppRequestService
.sendMessageHandler(
345 RequestCommand
.METER_VALUES
,
349 meterValue
: transactionEndMeterValue
,
353 stopResponse
= (await this.chargingStation
.ocppRequestService
.sendMessageHandler(
354 RequestCommand
.STOP_TRANSACTION
,
358 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
359 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
362 )) as StopTransactionResponse
;
363 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
366 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
367 transactionId ? ' ' + transactionId.toString() : ''
371 PerformanceStatistics
.endMeasure(measureId
, beginId
);
375 private logPrefix(connectorId
?: number): string {
377 return Utils
.logPrefix(
379 this.chargingStation
.stationInfo
.chargingStationId
+
380 ' | ATG on connector #' +
381 connectorId
.toString() +
385 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');