7315f73f98e6c7fcef257ceeaa1854d0d3f93495
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.js
1 const Configuration = require('../utils/Configuration');
2 const logger = require('../utils/Logger');
3 const WebSocket = require('ws');
4 const Constants = require('../utils/Constants');
5 const Utils = require('../utils/Utils');
6 const OCPPError = require('./OcppError');
7 const {v4: uuid} = require('uuid');
8 const AutomaticTransactionGenerator = require('./AutomaticTransactionGenerator');
9 const Statistics = require('../utils/Statistics');
10 const fs = require('fs');
11 const {performance, PerformanceObserver} = require('perf_hooks');
12
13 class 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 {
143 this.sendMessage(uuid(), this._bootNotificationMessage, Constants.OCPP_JSON_CALL_MESSAGE, 'BootNotification');
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 let [messageType, messageId, commandName, commandPayload, errorDetails] = [0, '', Constants.ENTITY_CHARGING_STATION, '', ''];
184 try {
185 // Parse the message
186 [messageType, messageId, commandName, commandPayload, errorDetails] = JSON.parse(message);
187
188 // Check the Type of message
189 switch (messageType) {
190 // Incoming Message
191 case Constants.OCPP_JSON_CALL_MESSAGE:
192 // Process the call
193 this._statistics.addMessage(commandName);
194 await this.handleRequest(messageId, commandName, commandPayload);
195 break;
196 // Outcome Message
197 case Constants.OCPP_JSON_CALL_RESULT_MESSAGE:
198 // Respond
199 // eslint-disable-next-line no-case-declarations
200 let responseCallback; let requestPayload;
201 if (Utils.isIterable(this._requests[messageId])) {
202 [responseCallback, , requestPayload] = this._requests[messageId];
203 } else {
204 throw new Error(`Response request for unknown message id ${messageId} is not iterable`);
205 }
206 if (!responseCallback) {
207 // Error
208 throw new Error(`Response for unknown message id ${messageId}`);
209 }
210 delete this._requests[messageId];
211 // this._statistics.addMessage(commandName)
212 responseCallback(commandName, requestPayload);
213 break;
214 // Error Message
215 case Constants.OCPP_JSON_CALL_ERROR_MESSAGE:
216 if (!this._requests[messageId]) {
217 // Error
218 throw new Error(`Error for unknown message id ${messageId}`);
219 }
220 // eslint-disable-next-line no-case-declarations
221 let rejectCallback;
222 if (Utils.isIterable(this._requests[messageId])) {
223 [, rejectCallback] = this._requests[messageId];
224 } else {
225 throw new Error(`Error request for unknown message id ${messageId} is not iterable`);
226 }
227 delete this._requests[messageId];
228 rejectCallback(new OCPPError(commandName, commandPayload, errorDetails));
229 break;
230 // Error
231 default:
232 throw new Error(`Wrong message type ${messageType}`);
233 }
234 } catch (error) {
235 // Log
236 logger.error('%s Incoming message %j processing error %s on request content %s', this._basicFormatLog(), message, error, this._requests[messageId]);
237 // Send error
238 // await this.sendError(messageId, error);
239 }
240 }
241
242 _reconnect(error) {
243 logger.error(this._basicFormatLog() + ' Socket: abnormally closed', error);
244 // Stop heartbeat interval
245 if (this._heartbeatSetInterval) {
246 clearInterval(this._heartbeatSetInterval);
247 this._heartbeatSetInterval = null;
248 }
249 // Stop the ATG
250 if (this._stationInfo.AutomaticTransactionGenerator.enable && this._automaticTransactionGeneration &&
251 !this._automaticTransactionGeneration._timeToStop) {
252 this._automaticTransactionGeneration.stop();
253 }
254 if (this._autoReconnectTimeout !== 0 &&
255 (this._autoReconnectRetryCount < this._autoReconnectMaxRetries || this._autoReconnectMaxRetries === -1)) {
256 logger.error(`${this._basicFormatLog()} Socket: connection retry with timeout ${this._autoReconnectTimeout}ms`);
257 this._autoReconnectRetryCount++;
258 setTimeout(() => {
259 logger.error(this._basicFormatLog() + ' Socket: reconnecting try #' + this._autoReconnectRetryCount);
260 this.start();
261 }, this._autoReconnectTimeout);
262 } else if (this._autoReconnectTimeout !== 0 || this._autoReconnectMaxRetries !== -1) {
263 logger.error(`${this._basicFormatLog()} Socket: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._autoReconnectTimeout})`);
264 }
265 }
266
267 send(command, messageType = Constants.OCPP_JSON_CALL_MESSAGE) {
268 // Send Message
269 return this.sendMessage(uuid(), command, messageType);
270 }
271
272 sendError(messageId, err) {
273 // Check exception: only OCPP error are accepted
274 const error = (err instanceof OCPPError ? err : new OCPPError(Constants.OCPP_ERROR_INTERNAL_ERROR, err.message));
275 // Send error
276 return this.sendMessage(messageId, error, Constants.OCPP_JSON_CALL_ERROR_MESSAGE);
277 }
278
279 sendMessage(messageId, command, messageType = Constants.OCPP_JSON_CALL_RESULT_MESSAGE, commandName = '') {
280 // Send a message through wsConnection
281 const self = this;
282 // Create a promise
283 return new Promise((resolve, reject) => {
284 let messageToSend;
285 // Type of message
286 switch (messageType) {
287 // Request
288 case Constants.OCPP_JSON_CALL_MESSAGE:
289 this._statistics.addMessage(commandName);
290 // Build request
291 this._requests[messageId] = [responseCallback, rejectCallback, command];
292 messageToSend = JSON.stringify([messageType, messageId, commandName, command]);
293 break;
294 // Response
295 case Constants.OCPP_JSON_CALL_RESULT_MESSAGE:
296 // Build response
297 messageToSend = JSON.stringify([messageType, messageId, command]);
298 break;
299 // Error Message
300 case Constants.OCPP_JSON_CALL_ERROR_MESSAGE:
301 // Build Message
302 this._statistics.addMessage(`Error ${command.code}`);
303 messageToSend = JSON.stringify([messageType, messageId, command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : '', command.details ? command.details : {}]);
304 break;
305 }
306 // Check if wsConnection in ready
307 if (this._wsConnection.readyState === WebSocket.OPEN) {
308 // Yes: Send Message
309 this._wsConnection.send(messageToSend);
310 } else {
311 // Buffer message until connection is back
312 this._messageQueue.push(messageToSend);
313 }
314 // Request?
315 if (messageType !== Constants.OCPP_JSON_CALL_MESSAGE) {
316 // Yes: send Ok
317 resolve();
318 } else if (this._wsConnection.readyState === WebSocket.OPEN) {
319 // Send timeout in case connection is open otherwise wait for ever
320 // FIXME: Handle message on timeout
321 setTimeout(() => rejectCallback(`Timeout for message ${messageId}`), Constants.OCPP_SOCKET_TIMEOUT);
322 }
323
324 // Function that will receive the request's response
325 function responseCallback(payload, requestPayload) {
326 self._statistics.addMessage(commandName, true);
327 const responseCallbackFn = 'handleResponse' + commandName;
328 if (typeof self[responseCallbackFn] === 'function') {
329 self[responseCallbackFn](payload, requestPayload, self);
330 } else {
331 // logger.error(this._basicFormatLog() + ' Trying to call an undefined callback function: ' + responseCallbackFn)
332 }
333 // Send the response
334 resolve(payload);
335 }
336
337 // Function that will receive the request's rejection
338 function rejectCallback(reason) {
339 // Build Exception
340 // eslint-disable-next-line no-empty-function
341 self._requests[messageId] = [() => { }, () => { }, '']; // Properly format the request
342 const error = reason instanceof OCPPError ? reason : new Error(reason);
343 // Send error
344 reject(error);
345 }
346 });
347 }
348
349 handleResponseBootNotification(payload) {
350 if (payload.status === 'Accepted') {
351 this._heartbeatInterval = payload.interval * 1000;
352 this.basicStartMessageSequence();
353 }
354 }
355
356 async basicStartMessageSequence() {
357 this._startHeartbeat(this);
358 if (!this._connectors) { // build connectors
359 this._connectors = {};
360 const connectorsConfig = JSON.parse(JSON.stringify(this._stationInfo.Connectors));
361 // determine number of customized connectors
362 let lastConnector;
363 for (lastConnector in connectorsConfig) {
364 if (lastConnector === 0 && this._stationInfo.usedConnectorId0) {
365 this._connectors[lastConnector] = connectorsConfig[lastConnector];
366 }
367 }
368 let maxConnectors = 0;
369 if (Array.isArray(this._stationInfo.numberOfConnectors)) {
370 // generate some connectors
371 maxConnectors = this._stationInfo.numberOfConnectors[(this._index - 1) % this._stationInfo.numberOfConnectors.length];
372 } else {
373 maxConnectors = this._stationInfo.numberOfConnectors;
374 }
375 // generate all connectors
376 for (let index = 1; index <= maxConnectors; index++) {
377 const randConnectorID = (this._stationInfo.randomConnectors ? Utils.getRandomInt(lastConnector, 1) : index);
378 this._connectors[index] = connectorsConfig[randConnectorID];
379 }
380 }
381
382 for (const connector in this._connectors) {
383 if (!this._connectors[connector].transactionStarted) {
384 if (this._connectors[connector].bootStatus) {
385 setTimeout(() => this.sendStatusNotification(connector, this._connectors[connector].bootStatus), 500);
386 } else {
387 setTimeout(() => this.sendStatusNotification(connector, 'Available'), 500);
388 }
389 } else {
390 setTimeout(() => this.sendStatusNotification(connector, 'Charging'), 500);
391 }
392 }
393
394 if (this._stationInfo.AutomaticTransactionGenerator.enable) {
395 if (!this._automaticTransactionGeneration) {
396 this._automaticTransactionGeneration = new AutomaticTransactionGenerator(this);
397 }
398 this._automaticTransactionGeneration.start();
399 }
400 this._statistics.start();
401 }
402
403 handleResponseStartTransaction(payload, requestPayload) {
404 this._connectors[requestPayload.connectorId] = {
405 transactionStarted: false,
406 idTag: requestPayload.idTag,
407 };
408 if (payload.idTagInfo.status === 'Accepted') {
409 for (const connector in this._connectors) {
410 if (connector === requestPayload.connectorId) {
411 this._connectors[connector].transactionStarted = true;
412 this._connectors[connector].transactionId = payload.transactionId;
413 this._connectors[connector].lastConsumptionValue = 0;
414 this._connectors[connector].lastSoC = 0;
415 logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[connector].transactionId + ' STARTED on ' + this._stationInfo.name + '#' + requestPayload.connectorId);
416 this.sendStatusNotification(requestPayload.connectorId, 'Charging');
417 const configuredMeterInterval = this._configuration.configurationKey.find((value) => value.key === 'meterValueInterval');
418 this.startMeterValues(requestPayload.connectorId,
419 (configuredMeterInterval ? configuredMeterInterval.value * 1000 : 60000),
420 this);
421 }
422 }
423 } else {
424 logger.error(this._basicFormatLog() + ' Starting transaction id ' + payload.transactionId + ' REJECTED with status ' + payload.idTagInfo.status + ', idTag ' + requestPayload.idTag);
425 for (const connector in this._connectors) {
426 if (connector === requestPayload.connectorId) {
427 this._resetTransactionOnConnector(connector);
428 }
429 }
430 this.sendStatusNotification(requestPayload.connectorId, 'Available');
431 }
432 }
433
434 async sendStatusNotification(connectorId, status, errorCode = 'NoError') {
435 try {
436 const payload = {
437 connectorId,
438 errorCode,
439 status,
440 };
441 await this.sendMessage(uuid(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StatusNotification');
442 } catch (error) {
443 logger.error(this._basicFormatLog() + ' Send status error: ' + error);
444 }
445 }
446
447 // eslint-disable-next-line class-methods-use-this
448 async _startHeartbeat(self) {
449 if (self._heartbeatInterval && !self._heartbeatSetInterval) {
450 logger.info(self._basicFormatLog() + ' Heartbeat started every ' + self._heartbeatInterval + 'ms');
451 self._heartbeatSetInterval = setInterval(() => {
452 try {
453 const payload = {
454 currentTime: new Date().toISOString(),
455 };
456 self.sendMessage(uuid(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'Heartbeat');
457 } catch (error) {
458 logger.error(self._basicFormatLog() + ' Send heartbeat error: ' + error);
459 }
460 }, self._heartbeatInterval);
461 } else {
462 logger.error(self._basicFormatLog() + ' Heartbeat interval undefined, not starting the heartbeat');
463 }
464 }
465
466 async handleRequest(messageId, commandName, commandPayload) {
467 let result;
468 this._statistics.addMessage(commandName, true);
469 // Call
470 if (typeof this['handle' + commandName] === 'function') {
471 try {
472 // Call the method
473 result = await this['handle' + commandName](commandPayload);
474 } catch (error) {
475 // Log
476 logger.error(this._basicFormatLog() + ' Handle request error: ' + error);
477 // Send back response to inform back end
478 await this.sendError(messageId, error);
479 }
480 } else {
481 // Throw Exception
482 await this.sendError(messageId, new OCPPError(Constants.OCPP_ERROR_NOT_IMPLEMENTED, 'Not implemented', {}));
483 throw new Error(`${commandName} is not implemented ${JSON.stringify(commandPayload, null, ' ')}`);
484 }
485 // Send Response
486 await this.sendMessage(messageId, result, Constants.OCPP_JSON_CALL_RESULT_MESSAGE);
487 }
488
489 async handleGetConfiguration() {
490 return this._configuration;
491 }
492
493 async handleChangeConfiguration(commandPayload) {
494 const keyToChange = this._configuration.configurationKey.find((element) => element.key === commandPayload.key);
495 if (keyToChange) {
496 keyToChange.value = commandPayload.value;
497 return Constants.OCPP_RESPONSE_ACCEPTED;
498 }
499 return Constants.OCPP_RESPONSE_REJECTED;
500 }
501
502 async handleRemoteStartTransaction(commandPayload) {
503 const transactionConnectorID = (commandPayload.connectorId ? commandPayload.connectorId : '1');
504 if (this.isAuthorizationRequested() && this._authorizeRemoteTxRequests) {
505 // Check if authorized
506 if (this._authorizedKeys.find((value) => value === commandPayload.idTag)) {
507 // Authorization successful start transaction
508 setTimeout(() => this.sendStartTransaction(transactionConnectorID, commandPayload.idTag), 500);
509 return Constants.OCPP_RESPONSE_ACCEPTED;
510 }
511 // Start authorization checks
512 return Constants.OCPP_RESPONSE_REJECTED;
513 }
514 // No local authorization check required => start transaction
515 setTimeout(() => this.sendStartTransaction(transactionConnectorID, commandPayload.idTag), 500);
516 return Constants.OCPP_RESPONSE_ACCEPTED;
517 }
518
519 async sendStartTransaction(connectorID, idTag) {
520 try {
521 const payload = {
522 connectorId: connectorID,
523 idTag,
524 meterStart: 0,
525 timestamp: new Date().toISOString(),
526 };
527 return await this.sendMessage(uuid(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StartTransaction');
528 } catch (error) {
529 logger.error(this._basicFormatLog() + ' Send start transaction error: ' + error);
530 this._resetTransactionOnConnector(connectorID);
531 throw error;
532 }
533 }
534
535 async sendStopTransaction(transactionId, connectorID) {
536 try {
537 const payload = {
538 transactionId,
539 meterStop: 0,
540 timestamp: new Date().toISOString(),
541 };
542 await this.sendMessage(uuid(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'StopTransaction');
543 logger.info(this._basicFormatLog() + ' Transaction ' + this._connectors[connectorID].transactionId + ' STOPPED on ' + this._stationInfo.name + '#' + connectorID);
544 this.sendStatusNotification(connectorID, 'Available');
545 } catch (error) {
546 logger.error(this._basicFormatLog() + ' Send stop transaction error: ' + error);
547 throw error;
548 } finally {
549 this._resetTransactionOnConnector(connectorID);
550 }
551 }
552
553 _resetTransactionOnConnector(connectorID) {
554 this._connectors[connectorID].transactionStarted = false;
555 this._connectors[connectorID].transactionId = null;
556 this._connectors[connectorID].lastConsumptionValue = -1;
557 this._connectors[connectorID].lastSoC = -1;
558 if (this._connectors[connectorID].transactionInterval) {
559 clearInterval(this._connectors[connectorID].transactionInterval);
560 }
561 }
562
563 // eslint-disable-next-line class-methods-use-this
564 async sendMeterValues(connectorID, interval, self) {
565 try {
566 const sampledValueLcl = {
567 timestamp: new Date().toISOString(),
568 };
569 const meterValuesClone = JSON.parse(JSON.stringify(self._getConnector(connectorID).MeterValues));
570 if (Array.isArray(meterValuesClone)) {
571 sampledValueLcl.sampledValue = meterValuesClone;
572 } else {
573 sampledValueLcl.sampledValue = [meterValuesClone];
574 }
575 for (let index = 0; index < sampledValueLcl.sampledValue.length; index++) {
576 if (sampledValueLcl.sampledValue[index].measurand && sampledValueLcl.sampledValue[index].measurand === 'SoC') {
577 sampledValueLcl.sampledValue[index].value = Math.floor(Math.random() * 100) + 1;
578 if (sampledValueLcl.sampledValue[index].value > 100) {
579 logger.info(self._basicFormatLog() + ' Meter type: ' +
580 (sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'default') +
581 ' value: ' + sampledValueLcl.sampledValue[index].value);
582 }
583 } else {
584 // Persist previous value in connector
585 const connector = self._connectors[connectorID];
586 let consumption;
587 consumption = Utils.getRandomInt(self._stationInfo.maxPower / 3600000 * interval, 3);
588 if (connector && connector.lastConsumptionValue >= 0) {
589 connector.lastConsumptionValue += consumption;
590 } else {
591 connector.lastConsumptionValue = 0;
592 }
593 consumption = Math.round(connector.lastConsumptionValue * 3600 / interval);
594 logger.info(self._basicFormatLog() + ' ConnectorID ' + connectorID + ' transaction ' + connector.transactionId + ' value ' + connector.lastConsumptionValue);
595 sampledValueLcl.sampledValue[index].value = connector.lastConsumptionValue;
596 if (sampledValueLcl.sampledValue[index].value > (self._stationInfo.maxPower * 3600 / interval) || sampledValueLcl.sampledValue[index].value < 500) {
597 logger.info(self._basicFormatLog() + ' Meter type: ' +
598 (sampledValueLcl.sampledValue[index].measurand ? sampledValueLcl.sampledValue[index].measurand : 'default') +
599 ' value: ' + sampledValueLcl.sampledValue[index].value + '/' + (self._stationInfo.maxPower * 3600 / interval));
600 }
601 }
602 }
603
604 const payload = {
605 connectorId: connectorID,
606 transactionId: self._connectors[connectorID].transactionId,
607 meterValue: [sampledValueLcl],
608 };
609 await self.sendMessage(uuid(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'MeterValues');
610 } catch (error) {
611 logger.error(self._basicFormatLog() + ' Send meter values error: ' + error);
612 }
613 }
614
615 async startMeterValues(connectorID, interval, self) {
616 // if (!this._connectors[connectorID].transactionStarted) {
617 // logger.debug(`${self._basicFormatLog()} Trying to start meter values on connector ID ${connectorID} with no transaction`);
618 // } else if (this._connectors[connectorID].transactionStarted && !this._connectors[connectorID].transactionId) {
619 // logger.debug(`${self._basicFormatLog()} Trying to start meter values on connector ID ${connectorID} with no transaction id`);
620 // }
621 this._connectors[connectorID].transactionInterval = setInterval(async () => {
622 const sendMeterValues = performance.timerify(this.sendMeterValues);
623 this._performanceObserver.observe({
624 entryTypes: ['function'],
625 });
626 await sendMeterValues(connectorID, interval, self);
627 }, interval);
628 }
629
630 async handleRemoteStopTransaction(commandPayload) {
631 for (const connector in this._connectors) {
632 if (this._connectors[connector].transactionId === commandPayload.transactionId) {
633 this.sendStopTransaction(commandPayload.transactionId, connector);
634 }
635 }
636 return Constants.OCPP_RESPONSE_ACCEPTED;
637 }
638
639 isAuthorizationRequested() {
640 return this._authorizedKeys && this._authorizedKeys.length > 0;
641 }
642
643 getRandomTagId() {
644 const index = Math.round(Math.floor(Math.random() * this._authorizedKeys.length - 1));
645 return this._authorizedKeys[index];
646 }
647
648 _getConnector(number) {
649 return this._stationInfo.Connectors[number];
650 }
651 }
652
653 module.exports = ChargingStation;