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 { MeterValuesResponse
} from
'../types/ocpp/Responses';
14 import { OCPP16ServiceUtils
} from
'./ocpp/1.6/OCPP16ServiceUtils';
15 import PerformanceStatistics from
'../performance/PerformanceStatistics';
16 import { RequestCommand
} from
'../types/ocpp/Requests';
17 import { Status
} from
'../types/AutomaticTransactionGenerator';
18 import Utils from
'../utils/Utils';
19 import logger from
'../utils/Logger';
21 export default class AutomaticTransactionGenerator
{
22 private static readonly instances
: Map
<string, AutomaticTransactionGenerator
> = new Map
<
24 AutomaticTransactionGenerator
27 public started
: boolean;
28 private readonly chargingStation
: ChargingStation
;
29 private readonly connectorsStatus
: Map
<number, Status
>;
31 private constructor(chargingStation
: ChargingStation
) {
32 this.chargingStation
= chargingStation
;
33 this.connectorsStatus
= new Map
<number, Status
>();
34 this.stopConnectors();
38 public static getInstance(chargingStation
: ChargingStation
): AutomaticTransactionGenerator
{
39 if (!AutomaticTransactionGenerator
.instances
.has(chargingStation
.hashId
)) {
40 AutomaticTransactionGenerator
.instances
.set(
41 chargingStation
.hashId
,
42 new AutomaticTransactionGenerator(chargingStation
)
45 return AutomaticTransactionGenerator
.instances
.get(chargingStation
.hashId
);
48 public start(): void {
50 logger
.error(`${this.logPrefix()} trying to start while already started`);
53 this.startConnectors();
59 logger
.error(`${this.logPrefix()} trying to stop while not started`);
62 this.stopConnectors();
66 private startConnectors(): void {
68 this.connectorsStatus
?.size
> 0 &&
69 this.connectorsStatus
.size
!== this.chargingStation
.getNumberOfConnectors()
71 this.connectorsStatus
.clear();
73 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
74 if (connectorId
> 0) {
75 this.startConnector(connectorId
);
80 private stopConnectors(): void {
81 for (const connectorId
of this.chargingStation
.connectors
.keys()) {
82 if (connectorId
> 0) {
83 this.stopConnector(connectorId
);
88 private async internalStartConnector(connectorId
: number): Promise
<void> {
89 this.initStartConnectorStatus(connectorId
);
91 this.logPrefix(connectorId
) +
92 ' started on connector and will run for ' +
93 Utils
.formatDurationMilliSeconds(
94 this.connectorsStatus
.get(connectorId
).stopDate
.getTime() -
95 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
98 while (this.connectorsStatus
.get(connectorId
).start
) {
99 if (new Date() > this.connectorsStatus
.get(connectorId
).stopDate
) {
100 this.stopConnector(connectorId
);
103 if (!this.chargingStation
.isInAcceptedState()) {
105 this.logPrefix(connectorId
) +
106 ' entered in transaction loop while the charging station is not in accepted state'
108 this.stopConnector(connectorId
);
111 if (!this.chargingStation
.isChargingStationAvailable()) {
113 this.logPrefix(connectorId
) +
114 ' entered in transaction loop while the charging station is unavailable'
116 this.stopConnector(connectorId
);
119 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
123 )} entered in transaction loop while the connector ${connectorId} is unavailable`
125 this.stopConnector(connectorId
);
128 if (!this.chargingStation
?.ocppRequestService
) {
132 )} transaction loop waiting for charging station service to be initialized`
135 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
136 } while (!this.chargingStation
?.ocppRequestService
);
139 Utils
.getRandomInteger(
140 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
141 .maxDelayBetweenTwoTransactions
,
142 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
143 .minDelayBetweenTwoTransactions
146 this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
)
148 await Utils
.sleep(wait
);
149 const start
= Utils
.secureRandom();
151 start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
153 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
155 const startResponse
= await this.startTransaction(connectorId
);
156 this.connectorsStatus
.get(connectorId
).startTransactionRequests
++;
157 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
158 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
159 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
++;
161 // Wait until end of transaction
163 Utils
.getRandomInteger(
164 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
165 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
168 this.logPrefix(connectorId
) +
170 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString() +
171 ' started and will stop in ' +
172 Utils
.formatDurationMilliSeconds(waitTrxEnd
)
174 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
++;
175 await Utils
.sleep(waitTrxEnd
);
178 this.logPrefix(connectorId
) +
179 ' stop transaction ' +
180 this.chargingStation
.getConnectorStatus(connectorId
).transactionId
.toString()
182 await this.stopTransaction(connectorId
);
185 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
186 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
188 this.logPrefix(connectorId
) +
189 ' skipped consecutively ' +
190 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() +
192 this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() +
196 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
198 await this.stopTransaction(connectorId
);
199 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
201 this.logPrefix(connectorId
) +
202 ' stopped on connector and lasted for ' +
203 Utils
.formatDurationMilliSeconds(
204 this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() -
205 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
209 `${this.logPrefix(connectorId)} connector status %j`,
210 this.connectorsStatus
.get(connectorId
)
214 private startConnector(connectorId
: number): void {
215 // Avoid hogging the event loop with a busy loop
217 this.internalStartConnector(connectorId
).catch(() => {
218 /* This is intentional */
223 private stopConnector(connectorId
: number): void {
224 this.connectorsStatus
.set(connectorId
, {
225 ...this.connectorsStatus
.get(connectorId
),
230 private initStartConnectorStatus(connectorId
: number): void {
231 this.connectorsStatus
.get(connectorId
).authorizeRequests
=
232 this?.connectorsStatus
.get(connectorId
)?.authorizeRequests
?? 0;
233 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
=
234 this?.connectorsStatus
.get(connectorId
)?.acceptedAuthorizeRequests
?? 0;
235 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
=
236 this?.connectorsStatus
.get(connectorId
)?.rejectedAuthorizeRequests
?? 0;
237 this.connectorsStatus
.get(connectorId
).startTransactionRequests
=
238 this?.connectorsStatus
.get(connectorId
)?.startTransactionRequests
?? 0;
239 this.connectorsStatus
.get(connectorId
).acceptedStartTransactionRequests
=
240 this?.connectorsStatus
.get(connectorId
)?.acceptedStartTransactionRequests
?? 0;
241 this.connectorsStatus
.get(connectorId
).rejectedStartTransactionRequests
=
242 this?.connectorsStatus
.get(connectorId
)?.rejectedStartTransactionRequests
?? 0;
243 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
=
244 this?.connectorsStatus
.get(connectorId
)?.stopTransactionRequests
?? 0;
245 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
246 this.connectorsStatus
.get(connectorId
).skippedTransactions
=
247 this?.connectorsStatus
.get(connectorId
)?.skippedTransactions
?? 0;
248 const previousRunDuration
=
249 this?.connectorsStatus
.get(connectorId
)?.startDate
&&
250 this?.connectorsStatus
.get(connectorId
)?.lastRunDate
251 ? this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() -
252 this.connectorsStatus
.get(connectorId
).startDate
.getTime()
254 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
255 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(
256 this.connectorsStatus
.get(connectorId
).startDate
.getTime() +
257 (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
??
258 Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) *
263 this.connectorsStatus
.get(connectorId
).start
= true;
266 private async startTransaction(
268 ): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
269 const measureId
= 'StartTransaction with ATG';
270 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
271 let startResponse
: StartTransactionResponse
;
272 if (this.chargingStation
.hasAuthorizedTags()) {
273 const idTag
= this.chargingStation
.getRandomIdTag();
274 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
275 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
= idTag
;
277 const authorizeResponse
: AuthorizeResponse
=
278 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<AuthorizeResponse
>(
279 RequestCommand
.AUTHORIZE
,
284 this.connectorsStatus
.get(connectorId
).authorizeRequests
++;
285 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
286 this.connectorsStatus
.get(connectorId
).acceptedAuthorizeRequests
++;
287 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
290 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<StartTransactionResponse
>(
291 RequestCommand
.START_TRANSACTION
,
297 PerformanceStatistics
.endMeasure(measureId
, beginId
);
298 return startResponse
;
300 this.connectorsStatus
.get(connectorId
).rejectedAuthorizeRequests
++;
301 PerformanceStatistics
.endMeasure(measureId
, beginId
);
302 return authorizeResponse
;
304 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
307 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<StartTransactionResponse
>(
308 RequestCommand
.START_TRANSACTION
,
314 PerformanceStatistics
.endMeasure(measureId
, beginId
);
315 return startResponse
;
317 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
319 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<StartTransactionResponse
>(
320 RequestCommand
.START_TRANSACTION
,
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
<MeterValuesResponse
>(
349 RequestCommand
.METER_VALUES
,
353 meterValue
: transactionEndMeterValue
,
358 await this.chargingStation
.ocppRequestService
.sendMessageHandler
<StopTransactionResponse
>(
359 RequestCommand
.STOP_TRANSACTION
,
363 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
364 idTag
: this.chargingStation
.getTransactionIdTag(transactionId
),
368 this.connectorsStatus
.get(connectorId
).stopTransactionRequests
++;
371 `${this.logPrefix(connectorId)} trying to stop a not started transaction${
372 transactionId ? ' ' + transactionId.toString() : ''
376 PerformanceStatistics
.endMeasure(measureId
, beginId
);
380 private logPrefix(connectorId
?: number): string {
382 return Utils
.logPrefix(
384 this.chargingStation
.stationInfo
.chargingStationId
+
385 ' | ATG on connector #' +
386 connectorId
.toString() +
390 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');