Initial commit
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.js
CommitLineData
7dde0b73
JB
1const Configuration = require('../utils/Configuration');
2const logger = require('../utils/Logger');
3const WebSocket = require('ws');
4const {
5 OCPP_JSON_CALL_MESSAGE,
6 OCPP_JSON_CALL_RESULT_MESSAGE,
7 OCPP_JSON_CALL_ERROR_MESSAGE,
8 OCPP_ERROR_INTERNAL_ERROR,
9 OCPP_SOCKET_TIMEOUT,
10 OCPP_ERROR_NOT_IMPLEMENTED,
11} = require('../utils/Constants');
12const Utils = require('../utils/Utils');
13const OCPPError = require('./OcppError');
14const {v4: uuid} = require('uuid');
15const AutomaticTransactionGenerator = require('./AutomaticTransactionGenerator');
16const Statistics = require('../utils/Statistics');
17const fs = require('fs');
18const {performance, PerformanceObserver} = require('perf_hooks');
19
20class ChargingStation {
21 constructor(index, stationTemplate) {
22 this._requests = {};
23 this._autoReconnectRetryCount = 0;
24 this._autoReconnectMaxRetries = Configuration.getAutoReconnectMaxRetries(); // -1 for unlimited
25 this._autoReconnectTimeout = Configuration.getAutoReconnectTimeout() * 1000; // ms, zero for disabling
26 this._isSocketRestart = false;
27 this._stationInfo = this._buildChargingStation(index, stationTemplate);
28 this._statistics = new Statistics(this._stationInfo.name);
29 this._performanceObserver = new PerformanceObserver((list) => {
30 const entry = list.getEntries()[0];
31 this._statistics.logPerformance(entry, 'ChargingStation');
32 this._performanceObserver.disconnect();
33 });
34 this._index = index;
35 this._messageQueue = [];
36 this._bootNotificationMessage = {
37 chargePointModel: this._stationInfo.chargePointModel,
38 chargePointVendor: this._stationInfo.chargePointVendor,
39 };
40 this._configuration = this._getConfiguration(stationTemplate);
41 this._authorizationFile = this._getAuthorizationFile(stationTemplate);
42 this._supervisionUrl = this._getSupervisionURL(index, stationTemplate);
43 }
44
45 _basicFormatLog() {
46 return Utils.basicFormatLog(` ${this._stationInfo.name}:`);
47 }
48
49 // eslint-disable-next-line class-methods-use-this
50 _getConfiguration(stationTemplate) {
51 return stationTemplate.Configuration ? stationTemplate.Configuration : {};
52 }
53
54 // eslint-disable-next-line class-methods-use-this
55 _getAuthorizationFile(stationTemplate) {
56 return stationTemplate.authorizationFile ? stationTemplate.authorizationFile : '';
57 }
58
59 // eslint-disable-next-line class-methods-use-this
60 _getSupervisionURL(index, stationTemplate) {
61 const supervisionUrls = JSON.parse(JSON.stringify(stationTemplate.supervisionURL ? stationTemplate.supervisionURL : Configuration.getSupervisionURLs()));
62 let indexUrl = 0;
63 if (Array.isArray(supervisionUrls)) {
64 if (Configuration.getEquallySupervisionDistribution()) {
65 indexUrl = index % supervisionUrls.length;
66 } else {
67 // Get a random url
68 indexUrl = Math.floor(Math.random() * supervisionUrls.length);
69 }
70 return supervisionUrls[indexUrl];
71 }
72 return supervisionUrls;
73 }
74
75 // eslint-disable-next-line class-methods-use-this
76 _getStationName(index, stationTemplate) {
77 return stationTemplate.fixedName ? stationTemplate.baseName : stationTemplate.baseName + '-' + ('000000000' + index).substr(('000000000' + index).length - 4);
78 }
79
80 _getAuthorizeRemoteTxRequests() {
81 const authorizeRemoteTxRequests = this._configuration.configurationKey.find((configElement) => configElement.key === 'AuthorizeRemoteTxRequests');
82 return authorizeRemoteTxRequests ? authorizeRemoteTxRequests.value : false;
83 }
84
85 _buildChargingStation(index, stationTemplate) {
86 if (Array.isArray(stationTemplate.power)) {
87 stationTemplate.maxPower = stationTemplate.power[Math.floor(Math.random() * stationTemplate.power.length)];
88 } else {
89 stationTemplate.maxPower = stationTemplate.power;
90 }
91 stationTemplate.name = this._getStationName(index, stationTemplate);
92 return stationTemplate;
93 }
94
95 async start() {
96 logger.info(this._basicFormatLog() + ' Will communicate with ' + this._supervisionUrl);
97 this._url = this._supervisionUrl + '/' + this._stationInfo.name;
98 this._wsConnection = new WebSocket(this._url, 'ocpp1.6');
99 if (this._authorizationFile !== '') {
100 try {
101 // load file
102 const fileDescriptor = fs.openSync(this._authorizationFile, 'r');
103 this._authorizedKeys = JSON.parse(fs.readFileSync(fileDescriptor, 'utf8'));
104 fs.closeSync(fileDescriptor);
105 // get remote authorization logic
106 // FIXME: move to the constructor
107 this._authorizeRemoteTxRequests = this._getAuthorizeRemoteTxRequests();
108 // monitor authorization file
109 // eslint-disable-next-line no-unused-vars
110 fs.watchFile(this._authorizationFile, (current, previous) => {
111 try {
112 // reload file
113 const fileDescriptor = fs.openSync(this._authorizationFile, 'r');
114 this._authorizedKeys = JSON.parse(fs.readFileSync(fileDescriptor, 'utf8'));
115 fs.closeSync(fileDescriptor);
116 } catch (error) {
117 logger.error(this._basicFormatLog() + ' Authorization file error: ' + error);
118 }
119 });
120 } catch (error) {
121 logger.error(this._basicFormatLog() + ' Authorization file error: ' + error);
122 }
123 }
124 // Handle Socket incoming messages
125 this._wsConnection.on('message', this.onMessage.bind(this));
126 // Handle Socket error
127 this._wsConnection.on('error', this.onError.bind(this));
128 // Handle Socket close
129 this._wsConnection.on('close', this.onClose.bind(this));
130 // Handle Socket opening connection
131 this._wsConnection.on('open', this.onOpen.bind(this));
132 // Handle Socket ping
133 this._wsConnection.on('ping', this.onPing.bind(this));
134 }
135
136 onOpen() {
137 logger.info(`${this._basicFormatLog()} Is connected to server through ${this._url}`);
138 if (this._isSocketRestart) {
139 this.basicStartMessageSequence();
140 if (this._messageQueue.length > 0) {
141 this._messageQueue.forEach((message) => {
142 if (this._wsConnection.readyState === WebSocket.OPEN) {
143 this._wsConnection.send(message);
144 }
145 });
146 }
147 } else {
148 // At first start, send Bootnotification
149 try {
150 this.sendMessage(uuid(), this._bootNotificationMessage, OCPP_JSON_CALL_MESSAGE, 'BootNotification');
151 } catch (error) {
152 logger.error(this._basicFormatLog() + ' Send boot notification error: ' + error);
153 }
154 }
155 this._autoReconnectRetryCount = 0;
156 this._isSocketRestart = false;
157 }
158
159 onError(error) {
160 switch (error) {
161 case 'ECONNREFUSED':
162 this._isSocketRestart = true;
163 this._reconnect(error);
164 break;
165 default:
166 logger.error(this._basicFormatLog() + ' Socket error: ' + error);
167 break;
168 }
169 }
170
171 onClose(error) {
172 switch (error) {
173 case 1000: // Normal close
174 case 1005:
175 logger.info(this._basicFormatLog() + ' Socket normally closed ' + error);
176 this._autoReconnectRetryCount = 0;
177 break;
178 default: // Abnormal close
179 this._isSocketRestart = true;
180 this._reconnect(error);
181 break;
182 }
183 }
184
185 onPing() {
186 logger.info(this._basicFormatLog() + ' Has received a WS ping (rfc6455) from the server');
187 }
188
189 async onMessage(message) {
190 // Parse the message
191 const [messageType, messageId, commandName, commandPayload, errorDetails] = JSON.parse(message);
192
193 try {
194 // Check the Type of message
195 switch (messageType) {
196 // Incoming Message
197 case OCPP_JSON_CALL_MESSAGE:
198 // Process the call
199 this._statistics.addMessage(commandName);
200 await this.handleRequest(messageId, commandName, commandPayload);
201 break;
202 // Outcome Message
203 case OCPP_JSON_CALL_RESULT_MESSAGE:
204 // Respond
205 // eslint-disable-next-line no-case-declarations
206 let responseCallback; let requestPayload;
207 if (Utils.isIterable(this._requests[messageId])) {
208 [responseCallback, , requestPayload] = this._requests[messageId];
209 } else {
210 throw new Error(`Response request for unknown message id ${messageId} is not iterable`);
211 }
212 if (!responseCallback) {
213 // Error
214 throw new Error(`Response for unknown message id ${messageId}`);
215 }
216 delete this._requests[messageId];
217 // this._statistics.addMessage(commandName)
218 responseCallback(commandName, requestPayload);
219 break;
220 // Error Message
221 case OCPP_JSON_CALL_ERROR_MESSAGE:
222 if (!this._requests[messageId]) {
223 // Error
224 throw new Error(`Error for unknown message id ${messageId}`);
225 }
226 // eslint-disable-next-line no-case-declarations
227 let rejectCallback;
228 if (Utils.isIterable(this._requests[messageId])) {
229 [, rejectCallback] = this._requests[messageId];
230 } else {
231 throw new Error(`Error request for unknown message id ${messageId} is not iterable`);
232 }
233 delete this._requests[messageId];
234 rejectCallback(new OCPPError(commandName, commandPayload, errorDetails));
235 break;
236 // Error
237 default:
238 throw new Error(`Wrong message type ${messageType}`);
239 }
240 } catch (error) {
241 // Log
242 logger.error('%s Incoming message %j processing error %s on request content %s', this._basicFormatLog(), message, error, this._requests[messageId]);
243 // Send error
244 // await this.sendError(messageId, error);
245 }
246 }
247
248 _reconnect(error) {
249 logger.error(this._basicFormatLog() + ' Socket: abnormally closed', error);
250 // Stop heartbeat interval
251 if (this._heartbeatSetInterval) {
252 clearInterval(this._heartbeatSetInterval);
253 this._heartbeatSetInterval = null;
254 }
255 // Stop the ATG
256 if (this._stationInfo.AutomaticTransactionGenerator.enable && this._automaticTransactionGeneration &&
257 !this._automaticTransactionGeneration._timeToStop) {
258 this._automaticTransactionGeneration.stop();
259 }
260 if (this._autoReconnectTimeout !== 0 &&
261 (this._autoReconnectRetryCount < this._autoReconnectMaxRetries || this._autoReconnectMaxRetries === -1)) {
262 logger.error(`${this._basicFormatLog()} Socket: connection retry with timeout ${this._autoReconnectTimeout}ms`);
263 this._autoReconnectRetryCount++;
264 setTimeout(() => {
265 logger.error(this._basicFormatLog() + ' Socket: reconnecting try #' + this._autoReconnectRetryCount);
266 this.start();
267 }, this._autoReconnectTimeout);
268 } else if (this._autoReconnectTimeout !== 0 || this._autoReconnectMaxRetries !== -1) {
269 logger.error(`${this._basicFormatLog()} Socket: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._autoReconnectTimeout})`);
270 }
271 }
272
273 send(command, messageType = OCPP_JSON_CALL_MESSAGE) {
274 // Send Message
275 return this.sendMessage(uuid(), command, messageType);
276 }
277
278 sendError(messageId, err) {
279 // Check exception: only OCPP error are accepted
280 const error = (err instanceof OCPPError ? err : new OCPPError(OCPP_ERROR_INTERNAL_ERROR, err.message));
281 // Send error
282 return this.sendMessage(messageId, error, OCPP_JSON_CALL_ERROR_MESSAGE);
283 }
284
285 sendMessage(messageId, command, messageType = OCPP_JSON_CALL_RESULT_MESSAGE, commandName = '') {
286 // Send a message through wsConnection
287 const self = this;
288 // Create a promise
289 return new Promise((resolve, reject) => {
290 let messageToSend;
291 // Type of message
292 switch (messageType) {
293 // Request
294 case OCPP_JSON_CALL_MESSAGE:
295 this._statistics.addMessage(commandName);
296 // Build request
297 this._requests[messageId] = [responseCallback, rejectCallback, command];
298 messageToSend = JSON.stringify([messageType, messageId, commandName, command]);
299 break;
300 // Response
301 case OCPP_JSON_CALL_RESULT_MESSAGE:
302 // Build response
303 messageToSend = JSON.stringify([messageType, messageId, command]);
304 break;
305 // Error Message
306 case OCPP_JSON_CALL_ERROR_MESSAGE:
307 // Build Message
308 // eslint-disable-next-line no-case-declarations
309 const {
310 code,
311 message,
312 details,
313 } = command;
314 this._statistics.addMessage(`Error ${code}`);
315 messageToSend = JSON.stringify([messageType, messageId, code, message, details]);
316 break;
317 }
318 // Check if wsConnection in ready
319 if (this._wsConnection.readyState === WebSocket.OPEN) {
320 // Yes: Send Message
321 this._wsConnection.send(messageToSend);
322 } else {
323 // Buffer message until connection is back
324 this._messageQueue.push(messageToSend);
325 }
326 // Request?
327 if (messageType !== OCPP_JSON_CALL_MESSAGE) {
328 // Yes: send Ok
329 resolve();
330 } else if (this._wsConnection.readyState === WebSocket.OPEN) {
331 // Send timeout in case connection is open otherwise wait for ever
332 // FIXME: Handle message on timeout
333 setTimeout(() => rejectCallback(`Timeout for message ${messageId}`), OCPP_SOCKET_TIMEOUT);
334 }
335
336 // Function that will receive the request's response
337 function responseCallback(payload, requestPayload) {
338 self._statistics.addMessage(commandName, true);
339 const responseCallbackFn = 'handleResponse' + commandName;
340 if (typeof self[responseCallbackFn] === 'function') {
341 self[responseCallbackFn](payload, requestPayload, self);
342 } else {
343 // logger.error(this._basicFormatLog() + ' Trying to call an undefined callback function: ' + responseCallbackFn)
344 }
345 // Send the response
346 resolve(payload);
347 }
348
349 // Function that will receive the request's rejection
350 function rejectCallback(reason) {
351 // Build Exception
352 // eslint-disable-next-line no-empty-function
353 self._requests[messageId] = [() => { }, () => { }, '']; // Properly format the request
354 const error = reason instanceof OCPPError ? reason : new Error(reason);
355 // Send error
356 reject(error);
357 }
358 });
359 }
360
361 handleResponseBootNotification(payload) {
362 if (payload.status === 'Accepted') {
363 this._heartbeatInterval = payload.interval * 1000;
364 this.basicStartMessageSequence();
365 }
366 }
367
368 async basicStartMessageSequence() {
369 this._startHeartbeat(this);
370 if (!this._connectors) { // build connectors
371 this._connectors = {};
372 const connectorsConfig = JSON.parse(JSON.stringify(this._stationInfo.Connectors));
373 // determine number of customized connectors
374 let lastConnector;
375 for (lastConnector in connectorsConfig) {
376 if (lastConnector === 0 && this._stationInfo.usedConnectorId0) {
377 this._connectors[lastConnector] = connectorsConfig[lastConnector];
378 }
379 }
380 let maxConnectors = 0;
381 if (Array.isArray(this._stationInfo.numberOfConnectors)) {
382 // generate some connectors
383 maxConnectors = this._stationInfo.numberOfConnectors[(this._index - 1) % this._stationInfo.numberOfConnectors.length];
384 } else {
385 maxConnectors = this._stationInfo.numberOfConnectors;
386 }
387 // generate all connectors
388 for (let index = 1; index <= maxConnectors; index++) {
389 const randConnectorID = (this._stationInfo.randomConnectors ? Utils.getRandomInt(lastConnector, 1) : index);
390 this._connectors[index] = connectorsConfig[randConnectorID];
391 }
392 }
393
394 for (const connector in this._connectors) {
395 if (!this._connectors[connector].transactionStarted) {
396 if (this._connectors[connector].bootStatus) {
397 setTimeout(() => this.sendStatusNotification(connector, this._connectors[connector].bootStatus), 500);
398 } else {
399 setTimeout(() => this.sendStatusNotification(connector, 'Available'), 500);
400 }
401 } else {
402 setTimeout(() => this.sendStatusNotification(connector, 'Charging'), 500);
403 }
404 }
405
406 if (this._stationInfo.AutomaticTransactionGenerator.enable) {
407 if (!this._automaticTransactionGeneration) {
408 this._automaticTransactionGeneration = new AutomaticTransactionGenerator(this);
409 }
410 this._automaticTransactionGeneration.start();
411 }
412 this._statistics.start();
413 }
414
415 handleResponseStartTransaction(payload, requestPayload) {
416 this._connectors[requestPayload.connectorId] = {
417 transactionStarted: false,
418 idTag: requestPayload.idTag,
419 };
420 if (payload.idTagInfo.status === 'Accepted') {
421 for (const connector in this._connectors) {
422 if (connector === requestPayload.connectorId) {
423 this._connectors[connector].transactionStarted = true;
424 this._connectors[connector].transactionId = payload.transactionId;
425 this._connectors[connector].lastConsumptionValue = 0;
426 this._connectors[connector].lastSoC = 0;
427 logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[connector].transactionId + ' STARTED on ' + this._stationInfo.name + '#' + requestPayload.connectorId);
428 this.sendStatusNotification(requestPayload.connectorId, 'Charging');
429 const configuredMeterInterval = this._configuration.configurationKey.find((value) => value.key === 'meterValueInterval');
430 this.startMeterValues(requestPayload.connectorId,
431 (configuredMeterInterval ? configuredMeterInterval.value * 1000 : 60000),
432 this);
433 }
434 }
435 } else {
436 logger.error(this._basicFormatLog() + ' Starting transaction id ' + payload.transactionId + ' REJECTED with status ' + payload.idTagInfo.status + ', idTag ' + requestPayload.idTag);
437 for (const connector in this._connectors) {
438 if (connector === requestPayload.connectorId) {
439 this._resetTransactionOnConnector(connector);
440 }
441 }
442 this.sendStatusNotification(requestPayload.connectorId, 'Available');
443 }
444 }
445
446 async sendStatusNotification(connectorId, status, errorCode = 'NoError') {
447 try {
448 const payload = {
449 connectorId,
450 errorCode,
451 status,
452 };
453 await this.sendMessage(uuid(), payload, OCPP_JSON_CALL_MESSAGE, 'StatusNotification');
454 } catch (error) {
455 logger.error(this._basicFormatLog() + ' Send status error: ' + error);
456 }
457 }
458
459 // eslint-disable-next-line class-methods-use-this
460 async _startHeartbeat(self) {
461 if (self._heartbeatInterval && !self._heartbeatSetInterval) {
462 logger.info(self._basicFormatLog() + ' Heartbeat started every ' + self._heartbeatInterval + 'ms');
463 self._heartbeatSetInterval = setInterval(() => {
464 try {
465 const payload = {
466 currentTime: new Date().toISOString(),
467 };
468 self.sendMessage(uuid(), payload, OCPP_JSON_CALL_MESSAGE, 'Heartbeat');
469 } catch (error) {
470 logger.error(self._basicFormatLog() + ' Send heartbeat error: ' + error);
471 }
472 }, self._heartbeatInterval);
473 } else {
474 logger.error(self._basicFormatLog() + ' Heartbeat interval undefined, not starting the heartbeat');
475 }
476 }
477
478 async handleRequest(messageId, commandName, commandPayload) {
479 let result;
480 this._statistics.addMessage(commandName, true);
481 // Call
482 if (typeof this['handle' + commandName] === 'function') {
483 try {
484 // Call the method
485 result = await this['handle' + commandName](commandPayload);
486 } catch (error) {
487 // Log
488 logger.error(this._basicFormatLog() + ' Handle request error: ' + error);
489 // Send back response to inform back end
490 await this.sendError(messageId, error);
491 }
492 } else {
493 // Throw Exception
494 await this.sendError(messageId, new OCPPError(OCPP_ERROR_NOT_IMPLEMENTED, 'Not implemented', {}));
495 throw new Error(`${commandName} is not implemented ${JSON.stringify(commandPayload, null, ' ')}`);
496 }
497 // Send Response
498 await this.sendMessage(messageId, result, OCPP_JSON_CALL_RESULT_MESSAGE);
499 }
500
501 async handleGetConfiguration() {
502 return this._configuration;
503 }
504
505 async handleChangeConfiguration(commandPayload) {
506 const keyToChange = this._configuration.configurationKey.find((element) => element.key === commandPayload.key);
507 if (keyToChange) {
508 keyToChange.value = commandPayload.value;
509 return {
510 status: 'Accepted',
511 };
512 }
513 return {
514 status: 'Rejected',
515 };
516 }
517
518 async handleRemoteStartTransaction(commandPayload) {
519 const transactionConnectorID = (commandPayload.connectorId ? commandPayload.connectorId : '1');
520 if (this.isAuthorizationRequested() && this._authorizeRemoteTxRequests) {
521 // check if authorized
522 if (this._authorizedKeys.find((value) => value === commandPayload.idTag)) {
523 // Authorization successful start transaction
524 setTimeout(() => this.sendStartTransaction(transactionConnectorID, commandPayload.idTag), 500);
525 return {
526 status: 'Accepted',
527 };
528 }
529 // Start authorization checks
530 return {
531 status: 'Rejected',
532 };
533 }
534 // no local authorization check required => start transaction
535 setTimeout(() => this.sendStartTransaction(transactionConnectorID, commandPayload.idTag), 500);
536 return {
537 status: 'Accepted',
538 };
539 }
540
541 async sendStartTransaction(connectorID, idTag) {
542 try {
543 const payload = {
544 connectorId: connectorID,
545 idTag,
546 meterStart: 0,
547 timestamp: new Date().toISOString(),
548 };
549 return await this.sendMessage(uuid(), payload, OCPP_JSON_CALL_MESSAGE, 'StartTransaction');
550 } catch (error) {
551 logger.error(this._basicFormatLog() + ' Send start transaction error: ' + error);
552 this._resetTransactionOnConnector(connectorID);
553 throw error;
554 }
555 }
556
557 async sendStopTransaction(transactionId, connectorID) {
558 try {
559 const payload = {
560 transactionId,
561 meterStop: 0,
562 timestamp: new Date().toISOString(),
563 };
564 await this.sendMessage(uuid(), payload, OCPP_JSON_CALL_MESSAGE, 'StopTransaction');
565 logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[connectorID].transactionId + ' STOPPED on ' + this._stationInfo.name + '#' + connectorID);
566 this.sendStatusNotification(connectorID, 'Available');
567 } catch (error) {
568 logger.error(this._basicFormatLog() + ' Send stop transaction error: ' + error);
569 throw error;
570 } finally {
571 this._resetTransactionOnConnector(connectorID);
572 }
573 }
574
575 _resetTransactionOnConnector(connectorID) {
576 this._connectors[connectorID].transactionStarted = false;
577 this._connectors[connectorID].transactionId = null;
578 this._connectors[connectorID].lastConsumptionValue = -1;
579 this._connectors[connectorID].lastSoC = -1;
580 if (this._connectors[connectorID].transactionInterval) {
581 clearInterval(this._connectors[connectorID].transactionInterval);
582 }
583 }
584
585 // eslint-disable-next-line class-methods-use-this
586 async sendMeterValues(connectorID, interval, self) {
587 try {
588 const sampledValueLcl = {
589 timestamp: new Date().toISOString(),
590 };
591 const meterValuesClone = JSON.parse(JSON.stringify(self._getConnector(connectorID).MeterValues));
592 if (Array.isArray(meterValuesClone)) {
593 sampledValueLcl.sampledValue = meterValuesClone;
594 } else {
595 sampledValueLcl.sampledValue = [meterValuesClone];
596 }
597 for (let index = 0; index < sampledValueLcl.sampledValue.length; index++) {
598 if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'SoC') {
599 sampledValueLcl.sampledValue[index].value = Math.floor(Math.random() * 100) + 1;
600 if (sampledValueLcl.sampledValue[index].value > 100) {
601 logger.info(self._basicFormatLog() + ' Meter type: ' +
602 (sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'default') +
603 ' value: ' + sampledValueLcl.sampledValue[index].value);
604 }
605 } else {
606 // Persist previous value in connector
607 const connector = self._connectors[connectorID];
608 let consumption;
609 consumption = Utils.getRandomInt(self._stationInfo.maxPower / 3600000 * interval, 3);
610 if (connector && connector.lastConsumptionValue >= 0) {
611 connector.lastConsumptionValue += consumption;
612 } else {
613 connector.lastConsumptionValue = 0;
614 }
615 consumption = Math.round(connector.lastConsumptionValue * 3600 / interval);
616 logger.info(self._basicFormatLog() + ' ConnectorID ' + connectorID + ' transaction ' + connector.transactionId + ' value ' + connector.lastConsumptionValue);
617 sampledValueLcl.sampledValue[index].value = connector.lastConsumptionValue;
618 if (sampledValueLcl.sampledValue[index].value > (self._stationInfo.maxPower * 3600 / interval) || sampledValueLcl.sampledValue[index].value < 500) {
619 logger.info(self._basicFormatLog() + ' Meter type: ' +
620 (sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'default') +
621 ' value: ' + sampledValueLcl.sampledValue[index].value + '/' + (self._stationInfo.maxPower * 3600 / interval));
622 }
623 }
624 }
625
626 const payload = {
627 connectorId: connectorID,
628 transactionId: self._connectors[connectorID].transactionId,
629 meterValue: [sampledValueLcl],
630 };
631 await self.sendMessage(uuid(), payload, OCPP_JSON_CALL_MESSAGE, 'MeterValues');
632 } catch (error) {
633 logger.error(self._basicFormatLog() + ' Send meter values error: ' + error);
634 }
635 }
636
637 async startMeterValues(connectorID, interval, self) {
638 // if (!this._connectors[connectorID].transactionStarted) {
639 // logger.debug(`${self._basicFormatLog()} Trying to start meter values on connector ID ${connectorID} with no transaction`);
640 // } else if (this._connectors[connectorID].transactionStarted && !this._connectors[connectorID].transactionId) {
641 // logger.debug(`${self._basicFormatLog()} Trying to start meter values on connector ID ${connectorID} with no transaction id`);
642 // }
643 this._connectors[connectorID].transactionInterval = setInterval(async () => {
644 const sendMeterValues = performance.timerify(this.sendMeterValues);
645 this._performanceObserver.observe({
646 entryTypes: ['function'],
647 });
648 await sendMeterValues(connectorID, interval, self);
649 }, interval);
650 }
651
652 async handleRemoteStopTransaction(commandPayload) {
653 for (const connector in this._connectors) {
654 if (this._connectors[connector].transactionId === commandPayload.transactionId) {
655 this.sendStopTransaction(commandPayload.transactionId, connector);
656 }
657 }
658 return {
659 status: 'Accepted',
660 };
661 }
662
663 isAuthorizationRequested() {
664 return this._authorizedKeys && this._authorizedKeys.length > 0;
665 }
666
667 getRandomTagId() {
668 const index = Math.round(Math.floor(Math.random() * this._authorizedKeys.length - 1));
669 return this._authorizedKeys[index];
670 }
671
672 _getConnector(number) {
673 return this._stationInfo.Connectors[number];
674 }
675}
676
677module.exports = ChargingStation;