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