private _autoReconnectMaxRetries: number;
private _autoReconnectTimeout: number;
private _requests;
- private _messageQueue;
+ private _messageQueue: any[];
private _automaticTransactionGeneration: AutomaticTransactionGenerator;
private _authorizedTags: string[];
private _heartbeatInterval: number;
return this._stationInfo.Configuration ? this._stationInfo.Configuration : {};
}
- _getAuthorizationFile() {
+ _getAuthorizationFile() : string {
return this._stationInfo.authorizationFile && this._stationInfo.authorizationFile;
}
_loadAndGetAuthorizedTags(): string[] {
- let authorizedTags = [];
+ let authorizedTags: string[] = [];
const authorizationFile = this._getAuthorizationFile();
if (authorizationFile) {
try {
return authorizedTags;
}
- getRandomTagId() {
+ getRandomTagId(): string {
const index = Math.floor(Math.random() * this._authorizedTags.length);
return this._authorizedTags[index];
}
- hasAuthorizedTags() {
+ hasAuthorizedTags(): boolean {
return !Utils.isEmptyArray(this._authorizedTags);
}
- getEnableStatistics() {
+ getEnableStatistics(): boolean {
return !Utils.isUndefined(this._stationInfo.enableStatistics) ? Utils.convertToBoolean(this._stationInfo.enableStatistics) : true;
}
}
}
- _getNumberOfRunningTransactions() {
+ _getNumberOfRunningTransactions(): number {
let trxCount = 0;
for (const connector in this._connectors) {
if (this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
return trxCount;
}
- _getPowerDivider() {
+ _getPowerDivider(): number {
let powerDivider = this._getNumberOfConnectors();
if (this._stationInfo.powerSharedByConnectors) {
powerDivider = this._getNumberOfRunningTransactions();
return this._connectors[id];
}
- _getTemplateMaxNumberOfConnectors() {
+ _getTemplateMaxNumberOfConnectors(): number {
return Object.keys(this._stationInfo.Connectors).length;
}
- _getMaxNumberOfConnectors() {
+ _getMaxNumberOfConnectors(): number {
let maxConnectors = 0;
if (!Utils.isEmptyArray(this._stationInfo.numberOfConnectors)) {
// Distribute evenly the number of connectors
return this._connectors[0] ? Object.keys(this._connectors).length - 1 : Object.keys(this._connectors).length;
}
- _getVoltageOut() {
+ _getVoltageOut(): number {
const errMsg = `${this._logPrefix()} Unknown ${this._getPowerOutType()} powerOutType in template file ${this._stationTemplateFile}, cannot define default voltage out`;
let defaultVoltageOut;
switch (this._getPowerOutType()) {
return !Utils.isUndefined(this._stationInfo.voltageOut) ? Utils.convertToInt(this._stationInfo.voltageOut) : defaultVoltageOut;
}
- _getPowerOutType() {
+ _getPowerOutType(): string {
return !Utils.isUndefined(this._stationInfo.powerOutType) ? this._stationInfo.powerOutType : 'AC';
}
- _getSupervisionURL() {
+ _getSupervisionURL(): string {
const supervisionUrls = Utils.cloneObject(this._stationInfo.supervisionURL ? this._stationInfo.supervisionURL : Configuration.getSupervisionURLs());
let indexUrl = 0;
if (!Utils.isEmptyArray(supervisionUrls)) {
return supervisionUrls;
}
- _getAuthorizeRemoteTxRequests() {
+ _getAuthorizeRemoteTxRequests(): boolean {
const authorizeRemoteTxRequests = this._getConfigurationKey('AuthorizeRemoteTxRequests');
return authorizeRemoteTxRequests ? Utils.convertToBoolean(authorizeRemoteTxRequests.value) : false;
}
- _getLocalAuthListEnabled() {
+ _getLocalAuthListEnabled(): boolean {
const localAuthListEnabled = this._getConfigurationKey('LocalAuthListEnabled');
return localAuthListEnabled ? Utils.convertToBoolean(localAuthListEnabled.value) : false;
}
}
}
- _stopHeartbeat() {
+ _stopHeartbeat(): void {
if (this._heartbeatSetInterval) {
clearInterval(this._heartbeatSetInterval);
this._heartbeatSetInterval = null;
}
}
- _startAuthorizationFileMonitoring() {
- // eslint-disable-next-line no-unused-vars
+ _startAuthorizationFileMonitoring(): void {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
fs.watchFile(this._getAuthorizationFile(), (current, previous) => {
try {
logger.debug(this._logPrefix() + ' Authorization file ' + this._getAuthorizationFile() + ' have changed, reload');
});
}
- _startStationTemplateFileMonitoring() {
- // eslint-disable-next-line no-unused-vars
+ _startStationTemplateFileMonitoring(): void {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
fs.watchFile(this._stationTemplateFile, (current, previous) => {
try {
logger.debug(this._logPrefix() + ' Template file ' + this._stationTemplateFile + ' have changed, reload');
return;
}
if (interval > 0) {
- this.getConnector(connectorId).transactionSetInterval = setInterval(async () => {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
+ this.getConnector(connectorId).transactionSetInterval = setInterval(async (): Promise<void> => {
if (this.getEnableStatistics()) {
const sendMeterValues = performance.timerify(this.sendMeterValues);
this._performanceObserver.observe({
entryTypes: ['function'],
});
- await sendMeterValues(connectorId, interval, self);
+ await sendMeterValues(connectorId, interval, this);
} else {
- await this.sendMeterValues(connectorId, interval, self);
+ await this.sendMeterValues(connectorId, interval, this);
}
}, interval);
} else {
}
}
- start() {
+ start(): void {
if (!this._wsConnectionUrl) {
this._wsConnectionUrl = this._supervisionUrl + '/' + this._stationInfo.name;
}
this._wsConnection.on('ping', this.onPing.bind(this));
}
- async stop(reason = '') {
+ async stop(reason = ''): Promise<void> {
// Stop
await this._stopMessageSequence();
// eslint-disable-next-line guard-for-in
await this.sendStatusNotification(Utils.convertToInt(connector), 'Unavailable');
}
if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) {
- await this._wsConnection.close();
+ this._wsConnection.close();
}
}
- _reconnect(error) {
+ _reconnect(error): void {
logger.error(this._logPrefix() + ' Socket: abnormally closed', error);
// Stop the ATG if needed
if (Utils.convertToBoolean(this._stationInfo.AutomaticTransactionGenerator.enable) &&
switch (messageType) {
// Incoming Message
case Constants.OCPP_JSON_CALL_MESSAGE:
+ if (this.getEnableStatistics()) {
+ this._statistics.addMessage(commandName, messageType);
+ }
// Process the call
await this.handleRequest(messageId, commandName, commandPayload);
break;
break;
// Error
default:
- throw new Error(`Wrong message type ${messageType}`);
+ // eslint-disable-next-line no-case-declarations
+ const errMsg = `${this._logPrefix()} Wrong message type ${messageType}`;
+ logger.error(errMsg);
+ throw new Error(errMsg);
}
} catch (error) {
// Log
- logger.error('%s Incoming message %j processing error %s on request content %s', this._logPrefix(), message, error, this._requests[messageId]);
+ logger.error('%s Incoming message %j processing error %s on request content type %s', this._logPrefix(), message, error, this._requests[messageId]);
// Send error
- await this.sendError(messageId, error);
+ await this.sendError(messageId, error, commandName);
}
}
- sendHeartbeat() {
+ async sendHeartbeat() {
try {
const payload = {
currentTime: new Date().toISOString(),
};
- this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'Heartbeat');
+ await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'Heartbeat');
} catch (error) {
logger.error(this._logPrefix() + ' Send Heartbeat error: ' + error);
throw error;
}
}
- sendBootNotification() {
+ async sendBootNotification() {
try {
- this.sendMessage(Utils.generateUUID(), this._bootNotificationMessage, Constants.OCPP_JSON_CALL_MESSAGE, 'BootNotification');
+ await this.sendMessage(Utils.generateUUID(), this._bootNotificationMessage, Constants.OCPP_JSON_CALL_MESSAGE, 'BootNotification');
} catch (error) {
logger.error(this._logPrefix() + ' Send BootNotification error: ' + error);
throw error;
}
}
- async sendStatusNotification(connectorId: number, status, errorCode = 'NoError') {
+ async sendStatusNotification(connectorId: number, status: string, errorCode = 'NoError') {
try {
const payload = {
connectorId,
}
}
- async sendStartTransaction(connectorId: number, idTag?: string) {
+ async sendStartTransaction(connectorId: number, idTag?: string): Promise<unknown> {
try {
const payload = {
connectorId,
}
}
- async sendStopTransaction(transactionId, reason = ''): Promise<void> {
+ async sendStopTransaction(transactionId: number, reason = ''): Promise<void> {
try {
const payload = {
transactionId,
}
// eslint-disable-next-line consistent-this
- async sendMeterValues(connectorId: number, interval: number, self, debug = false): Promise<void> {
+ async sendMeterValues(connectorId: number, interval: number, self: ChargingStation, debug = false): Promise<void> {
try {
const sampledValues = {
timestamp: new Date().toISOString(),
}
// Voltage measurand
} else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'Voltage' && self._getConfigurationKey('MeterValuesSampledData').value.includes('Voltage')) {
+ const voltageMeasurandValue = Utils.getRandomFloatRounded(self._getVoltageOut() + self._getVoltageOut() * 0.1, self._getVoltageOut() - self._getVoltageOut() * 0.1);
sampledValues.sampledValue.push({
...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: 'V' },
...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
measurand: meterValuesTemplate[index].measurand,
...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
- ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: self._getVoltageOut() },
+ ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue },
});
for (let phase = 1; self._getNumberOfPhases() === 3 && phase <= self._getNumberOfPhases(); phase++) {
const voltageValue = sampledValues.sampledValue[sampledValues.sampledValue.length - 1].value;
...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
measurand: meterValuesTemplate[index].measurand,
...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
- ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: self._getVoltageOut() },
+ ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue },
phase: phaseValue,
});
}
}
}
- sendError(messageId, err) {
- // Check exception: only OCPP error are accepted
- const error = err instanceof OCPPError ? err : new OCPPError(Constants.OCPP_ERROR_INTERNAL_ERROR, err.message);
+ async sendError(messageId, err: Error|OCPPError, commandName) {
+ // Check exception type: only OCPP error are accepted
+ const error = err instanceof OCPPError ? err : new OCPPError(Constants.OCPP_ERROR_INTERNAL_ERROR, err.message, err.stack && err.stack);
// Send error
- return this.sendMessage(messageId, error, Constants.OCPP_JSON_CALL_ERROR_MESSAGE);
+ return this.sendMessage(messageId, error, Constants.OCPP_JSON_CALL_ERROR_MESSAGE, commandName);
}
- sendMessage(messageId, command, messageType = Constants.OCPP_JSON_CALL_RESULT_MESSAGE, commandName = '') {
+ async sendMessage(messageId, commandParams, messageType = Constants.OCPP_JSON_CALL_RESULT_MESSAGE, commandName: string) {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
// Send a message through wsConnection
return new Promise((resolve, reject) => {
switch (messageType) {
// Request
case Constants.OCPP_JSON_CALL_MESSAGE:
- if (this.getEnableStatistics()) {
- this._statistics.addMessage(commandName);
- }
// Build request
- this._requests[messageId] = [responseCallback, rejectCallback, command];
- messageToSend = JSON.stringify([messageType, messageId, commandName, command]);
+ this._requests[messageId] = [responseCallback, rejectCallback, commandParams];
+ messageToSend = JSON.stringify([messageType, messageId, commandName, commandParams]);
break;
// Response
case Constants.OCPP_JSON_CALL_RESULT_MESSAGE:
- if (this.getEnableStatistics()) {
- this._statistics.addMessage(commandName);
- }
// Build response
- messageToSend = JSON.stringify([messageType, messageId, command]);
+ messageToSend = JSON.stringify([messageType, messageId, commandParams]);
break;
// Error Message
case Constants.OCPP_JSON_CALL_ERROR_MESSAGE:
- if (this.getEnableStatistics()) {
- this._statistics.addMessage(commandName + ' error');
- }
// Build Error Message
- messageToSend = JSON.stringify([messageType, messageId, command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : '', command.details ? command.details : {}]);
+ messageToSend = JSON.stringify([messageType, messageId, commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : '', commandParams.details ? commandParams.details : {}]);
break;
}
// Check if wsConnection is ready
if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) {
+ if (this.getEnableStatistics()) {
+ this._statistics.addMessage(commandName, messageType);
+ }
// Yes: Send Message
this._wsConnection.send(messageToSend);
} else {
- // Buffer message until connection is back
- this._messageQueue.push(messageToSend);
+ let dups = false;
+ // Handle dups in buffer
+ for (const message of this._messageQueue) {
+ // Same message
+ if (JSON.stringify(messageToSend) === JSON.stringify(message)) {
+ dups = true;
+ break;
+ }
+ }
+ if (!dups) {
+ // Buffer message
+ this._messageQueue.push(messageToSend);
+ }
// Reject it
- return rejectCallback(new OCPPError(command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : `Web socket closed for message id '${messageId}' with content '${messageToSend}', buffer it`, command.details ? command.details : {}));
+ return rejectCallback(new OCPPError(commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : `Web socket closed for message id '${messageId}' with content '${messageToSend}', message buffered`, commandParams.details ? commandParams.details : {}));
}
// Response?
if (messageType === Constants.OCPP_JSON_CALL_RESULT_MESSAGE) {
resolve();
} else if (messageType === Constants.OCPP_JSON_CALL_ERROR_MESSAGE) {
// Send timeout
- setTimeout(() => rejectCallback(new OCPPError(command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : `Timeout for message id '${messageId}' with content '${messageToSend}'`, command.details ? command.details : {})), Constants.OCPP_SOCKET_TIMEOUT);
+ setTimeout(() => rejectCallback(new OCPPError(commandParams.code ? commandParams.code : Constants.OCPP_ERROR_GENERIC_ERROR, commandParams.message ? commandParams.message : `Timeout for message id '${messageId}' with content '${messageToSend}'`, commandParams.details ? commandParams.details : {})), Constants.OCPP_SOCKET_TIMEOUT);
}
// Function that will receive the request's response
- function responseCallback(payload, requestPayload) {
+ function responseCallback(payload, requestPayload): void {
+ if (self.getEnableStatistics()) {
+ self._statistics.addMessage(commandName, messageType);
+ }
// Send the response
self.handleResponse(commandName, payload, requestPayload);
resolve(payload);
}
// Function that will receive the request's rejection
- function rejectCallback(error: OCPPError) {
- logger.debug(`${self._logPrefix()} Error %j on commandName %s command %j`, error, commandName, command);
+ function rejectCallback(error: OCPPError): void {
if (self.getEnableStatistics()) {
- self._statistics.addMessage(`${commandName} error`, true);
+ self._statistics.addMessage(commandName, messageType);
}
+ logger.debug(`${self._logPrefix()} Error %j occurred when calling command %s with parameters %j`, error, commandName, commandParams);
// Build Exception
// eslint-disable-next-line no-empty-function
self._requests[messageId] = [() => { }, () => { }, '']; // Properly format the request
});
}
- handleResponse(commandName, payload, requestPayload) {
- if (this.getEnableStatistics()) {
- this._statistics.addMessage(commandName, true);
- }
+ handleResponse(commandName: string, payload, requestPayload) {
const responseCallbackFn = 'handleResponse' + commandName;
if (typeof this[responseCallbackFn] === 'function') {
this[responseCallbackFn](payload, requestPayload);
}
async handleRequest(messageId, commandName, commandPayload) {
- if (this.getEnableStatistics()) {
- this._statistics.addMessage(commandName, true);
- }
let response;
// Call
if (typeof this['handleRequest' + commandName] === 'function') {
// Log
logger.error(this._logPrefix() + ' Handle request error: ' + error);
// Send back response to inform backend
- await this.sendError(messageId, error);
+ await this.sendError(messageId, error, commandName);
+ throw error;
}
} else {
// Throw exception
- await this.sendError(messageId, new OCPPError(Constants.OCPP_ERROR_NOT_IMPLEMENTED, 'Not implemented', {}));
+ await this.sendError(messageId, new OCPPError(Constants.OCPP_ERROR_NOT_IMPLEMENTED, `${commandName} is not implemented`, {}), commandName);
throw new Error(`${commandName} is not implemented ${JSON.stringify(commandPayload, null, ' ')}`);
}
// Send response
- await this.sendMessage(messageId, response, Constants.OCPP_JSON_CALL_RESULT_MESSAGE);
+ await this.sendMessage(messageId, response, Constants.OCPP_JSON_CALL_RESULT_MESSAGE, commandName);
}
// Simulate charging station restart
// Check if authorized
if (this._authorizedTags.find((value) => value === commandPayload.idTag)) {
// Authorization successful start transaction
- this.sendStartTransaction(transactionConnectorID, commandPayload.idTag);
+ await this.sendStartTransaction(transactionConnectorID, commandPayload.idTag);
logger.debug(this._logPrefix() + ' Transaction remotely STARTED on ' + this._stationInfo.name + '#' + transactionConnectorID + ' for idTag ' + commandPayload.idTag);
return Constants.OCPP_RESPONSE_ACCEPTED;
}
return Constants.OCPP_RESPONSE_REJECTED;
}
// No local authorization check required => start transaction
- this.sendStartTransaction(transactionConnectorID, commandPayload.idTag);
+ await this.sendStartTransaction(transactionConnectorID, commandPayload.idTag);
logger.debug(this._logPrefix() + ' Transaction remotely STARTED on ' + this._stationInfo.name + '#' + transactionConnectorID + ' for idTag ' + commandPayload.idTag);
return Constants.OCPP_RESPONSE_ACCEPTED;
}
async handleRequestRemoteStopTransaction(commandPayload) {
for (const connector in this._connectors) {
if (this.getConnector(Utils.convertToInt(connector)).transactionId === commandPayload.transactionId) {
- this.sendStopTransaction(commandPayload.transactionId);
+ await this.sendStopTransaction(commandPayload.transactionId);
return Constants.OCPP_RESPONSE_ACCEPTED;
}
}