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