1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import { AuthorizationStatus
, AuthorizeResponse
, StartTransactionResponse
, StopTransactionReason
, StopTransactionResponse
} from
'../types/ocpp/Transaction';
5 import ChargingStation from
'./ChargingStation';
6 import Constants from
'../utils/Constants';
7 import PerformanceStatistics from
'../performance/PerformanceStatistics';
8 import { Status
} from
'../types/AutomaticTransactionGenerator';
9 import Utils from
'../utils/Utils';
10 import logger from
'../utils/Logger';
12 export default class AutomaticTransactionGenerator
{
13 public started
: boolean;
14 private chargingStation
: ChargingStation
;
15 private connectorsStatus
: Map
<number, Status
>;
17 constructor(chargingStation
: ChargingStation
) {
18 this.chargingStation
= chargingStation
;
19 this.connectorsStatus
= new Map
<number, Status
>();
20 this.stopConnectors();
24 public start(): void {
26 logger
.error(`${this.logPrefix()} trying to start while already started`);
29 this.startConnectors();
35 logger
.error(`${this.logPrefix()} trying to stop while not started`);
38 this.stopConnectors();
42 private startConnectors(): void {
43 for (const connector
in this.chargingStation
.connectors
) {
44 const connectorId
= Utils
.convertToInt(connector
);
45 if (connectorId
> 0) {
46 this.startConnector(connectorId
);
51 private stopConnectors(): void {
52 for (const connector
in this.chargingStation
.connectors
) {
53 const connectorId
= Utils
.convertToInt(connector
);
54 if (connectorId
> 0) {
55 this.stopConnector(connectorId
);
60 private async internalStartConnector(connectorId
: number): Promise
<void> {
61 this.initStartConnectorStatus(connectorId
);
62 logger
.info(this.logPrefix(connectorId
) + ' started on connector and will run for ' + Utils
.formatDurationMilliSeconds(this.connectorsStatus
.get(connectorId
).stopDate
.getTime() - this.connectorsStatus
.get(connectorId
).startDate
.getTime()));
63 while (this.connectorsStatus
.get(connectorId
).start
) {
64 if ((new Date()) > this.connectorsStatus
.get(connectorId
).stopDate
) {
65 this.stopConnector(connectorId
);
68 if (!this.chargingStation
.isRegistered()) {
69 logger
.error(this.logPrefix(connectorId
) + ' entered in transaction loop while the charging station is not registered');
70 this.stopConnector(connectorId
);
73 if (!this.chargingStation
.isChargingStationAvailable()) {
74 logger
.info(this.logPrefix(connectorId
) + ' entered in transaction loop while the charging station is unavailable');
75 this.stopConnector(connectorId
);
78 if (!this.chargingStation
.isConnectorAvailable(connectorId
)) {
79 logger
.info(`${this.logPrefix(connectorId)} entered in transaction loop while the connector ${connectorId} is unavailable`);
80 this.stopConnector(connectorId
);
83 if (!this.chargingStation
?.ocppRequestService
) {
84 logger
.info(`${this.logPrefix(connectorId)} transaction loop waiting for charging station service to be initialized`);
86 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_INITIALIZATION_TIME
);
87 } while (!this.chargingStation
?.ocppRequestService
);
89 const wait
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDelayBetweenTwoTransactions
,
90 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDelayBetweenTwoTransactions
) * 1000;
91 logger
.info(this.logPrefix(connectorId
) + ' waiting for ' + Utils
.formatDurationMilliSeconds(wait
));
92 await Utils
.sleep(wait
);
93 const start
= Utils
.secureRandom();
94 if (start
< this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.probabilityOfStart
) {
95 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
97 const startResponse
= await this.startTransaction(connectorId
);
98 if (startResponse
?.idTagInfo
?.status !== AuthorizationStatus
.ACCEPTED
) {
99 logger
.warn(this.logPrefix(connectorId
) + ' start transaction rejected');
100 await Utils
.sleep(Constants
.CHARGING_STATION_ATG_WAIT_TIME
);
102 // Wait until end of transaction
103 const waitTrxEnd
= Utils
.getRandomInteger(this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.maxDuration
,
104 this.chargingStation
.stationInfo
.AutomaticTransactionGenerator
.minDuration
) * 1000;
105 logger
.info(this.logPrefix(connectorId
) + ' transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString() + ' started and will stop in ' + Utils
.formatDurationMilliSeconds(waitTrxEnd
));
106 await Utils
.sleep(waitTrxEnd
);
108 logger
.info(this.logPrefix(connectorId
) + ' stop transaction ' + this.chargingStation
.getConnector(connectorId
).transactionId
.toString());
109 await this.stopTransaction(connectorId
);
112 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
++;
113 this.connectorsStatus
.get(connectorId
).skippedTransactions
++;
114 logger
.info(this.logPrefix(connectorId
) + ' skipped consecutively ' + this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
.toString() + '/' + this.connectorsStatus
.get(connectorId
).skippedTransactions
.toString() + ' transaction(s)');
116 this.connectorsStatus
.get(connectorId
).lastRunDate
= new Date();
118 await this.stopTransaction(connectorId
);
119 this.connectorsStatus
.get(connectorId
).stoppedDate
= new Date();
120 logger
.info(this.logPrefix(connectorId
) + ' stopped on connector and lasted for ' + Utils
.formatDurationMilliSeconds(this.connectorsStatus
.get(connectorId
).stoppedDate
.getTime() - this.connectorsStatus
.get(connectorId
).startDate
.getTime()));
121 logger
.debug(`${this.logPrefix(connectorId)} connector status %j`, this.connectorsStatus
.get(connectorId
));
124 private startConnector(connectorId
: number): void {
125 // Avoid hogging the event loop with a busy loop
127 this.internalStartConnector(connectorId
).catch(() => { /* This is intentional */ });
131 private stopConnector(connectorId
: number): void {
132 this.connectorsStatus
.set(connectorId
, { ...this.connectorsStatus
.get(connectorId
), start
: false });
135 private initStartConnectorStatus(connectorId
: number): void {
136 this.connectorsStatus
.get(connectorId
).skippedConsecutiveTransactions
= 0;
137 this.connectorsStatus
.get(connectorId
).skippedTransactions
= 0;
138 const previousRunDuration
= (this?.connectorsStatus
.get(connectorId
)?.startDate
&& this?.connectorsStatus
.get(connectorId
)?.lastRunDate
)
139 ? (this.connectorsStatus
.get(connectorId
).lastRunDate
.getTime() - this.connectorsStatus
.get(connectorId
).startDate
.getTime())
141 this.connectorsStatus
.get(connectorId
).startDate
= new Date();
142 this.connectorsStatus
.get(connectorId
).stopDate
= new Date(this.connectorsStatus
.get(connectorId
).startDate
.getTime()
143 + (this.chargingStation
.stationInfo
?.AutomaticTransactionGenerator
?.stopAfterHours
?? Constants
.CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS
) * 3600 * 1000
144 - previousRunDuration
);
145 this.connectorsStatus
.get(connectorId
).start
= true;
148 private async startTransaction(connectorId
: number): Promise
<StartTransactionResponse
| AuthorizeResponse
> {
149 const measureId
= 'StartTransaction with ATG';
150 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
151 let startResponse
: StartTransactionResponse
;
152 if (this.chargingStation
.hasAuthorizedTags()) {
153 const idTag
= this.chargingStation
.getRandomIdTag();
154 if (this.chargingStation
.getAutomaticTransactionGeneratorRequireAuthorize()) {
156 const authorizeResponse
= await this.chargingStation
.ocppRequestService
.sendAuthorize(connectorId
, idTag
);
157 if (authorizeResponse
?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
) {
158 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
160 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
161 PerformanceStatistics
.endMeasure(measureId
, beginId
);
162 return startResponse
;
164 PerformanceStatistics
.endMeasure(measureId
, beginId
);
165 return authorizeResponse
;
167 logger
.info(this.logPrefix(connectorId
) + ' start transaction for idTag ' + idTag
);
169 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
, idTag
);
170 PerformanceStatistics
.endMeasure(measureId
, beginId
);
171 return startResponse
;
173 logger
.info(this.logPrefix(connectorId
) + ' start transaction without an idTag');
174 startResponse
= await this.chargingStation
.ocppRequestService
.sendStartTransaction(connectorId
);
175 PerformanceStatistics
.endMeasure(measureId
, beginId
);
176 return startResponse
;
179 private async stopTransaction(connectorId
: number, reason
: StopTransactionReason
= StopTransactionReason
.NONE
): Promise
<StopTransactionResponse
> {
180 const measureId
= 'StopTransaction with ATG';
181 const beginId
= PerformanceStatistics
.beginMeasure(measureId
);
182 let transactionId
= 0;
183 let stopResponse
: StopTransactionResponse
;
184 if (this.chargingStation
.getConnector(connectorId
)?.transactionStarted
) {
185 transactionId
= this.chargingStation
.getConnector(connectorId
).transactionId
;
186 stopResponse
= await this.chargingStation
.ocppRequestService
.sendStopTransaction(transactionId
,
187 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
),
188 this.chargingStation
.getTransactionIdTag(transactionId
),
191 logger
.warn(`${this.logPrefix(connectorId)} trying to stop a not started transaction${transactionId ? ' ' + transactionId.toString() : ''}`);
193 PerformanceStatistics
.endMeasure(measureId
, beginId
);
197 private logPrefix(connectorId
?: number): string {
199 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG on connector #' + connectorId
.toString() + ':');
201 return Utils
.logPrefix(' ' + this.chargingStation
.stationInfo
.chargingStationId
+ ' | ATG:');