export default class ChargingStation {
private _index: number;
- private _stationTemplateFile;
+ private _stationTemplateFile: string;
private _stationInfo;
private _bootNotificationMessage;
private _connectors;
private _configuration;
- private _connectorsConfigurationHash;
+ private _connectorsConfigurationHash: string;
private _supervisionUrl;
private _wsConnectionUrl;
- private _wsConnection;
+ private _wsConnection: WebSocket;
private _isSocketRestart;
- private _autoReconnectRetryCount;
- private _autoReconnectMaxRetries;
- private _autoReconnectTimeout;
+ private _autoReconnectRetryCount: number;
+ private _autoReconnectMaxRetries: number;
+ private _autoReconnectTimeout: number;
private _requests;
- private _messageQueue;
+ private _messageQueue: any[];
private _automaticTransactionGeneration: AutomaticTransactionGenerator;
private _authorizedTags: string[];
- private _heartbeatInterval;
+ private _heartbeatInterval: number;
private _heartbeatSetInterval;
private _statistics: Statistics;
private _performanceObserver: PerformanceObserver;
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;
}
- _basicStartMessageSequence(): void {
+ _startMessageSequence(): void {
// Start heartbeat
this._startHeartbeat();
// Initialize connectors status
for (const connector in this._connectors) {
if (!this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
if (this.getConnector(Utils.convertToInt(connector)).bootStatus) {
- this.sendStatusNotificationWithTimeout(connector, this.getConnector(Utils.convertToInt(connector)).bootStatus);
+ this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus);
} else {
- this.sendStatusNotificationWithTimeout(connector, 'Available');
+ this.sendStatusNotification(Utils.convertToInt(connector), 'Available');
}
} else {
- this.sendStatusNotificationWithTimeout(connector, 'Charging');
+ this.sendStatusNotification(Utils.convertToInt(connector), 'Charging');
}
}
// Start the ATG
}
}
+ async _stopMessageSequence(reason = ''): Promise<void> {
+ // Stop heartbeat
+ this._stopHeartbeat();
+ // Stop the ATG
+ if (Utils.convertToBoolean(this._stationInfo.AutomaticTransactionGenerator.enable) &&
+ this._automaticTransactionGeneration &&
+ !this._automaticTransactionGeneration.timeToStop) {
+ await this._automaticTransactionGeneration.stop(reason);
+ } else {
+ for (const connector in this._connectors) {
+ if (this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ await this.sendStopTransaction(this.getConnector(Utils.convertToInt(connector)).transactionId, reason);
+ }
+ }
+ }
+ }
+
_startHeartbeat(): void {
if (this._heartbeatInterval && this._heartbeatInterval > 0 && !this._heartbeatSetInterval) {
this._heartbeatSetInterval = setInterval(() => {
}
}
- _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');
// Initialize
this._initialize();
- this._addConfigurationKey('HeartBeatInterval', Utils.convertToInt(this._heartbeatInterval ? this._heartbeatInterval / 1000 : 0));
- this._addConfigurationKey('HeartbeatInterval', Utils.convertToInt(this._heartbeatInterval ? this._heartbeatInterval / 1000 : 0), false, false);
+ if (!Utils.convertToBoolean(this._stationInfo.AutomaticTransactionGenerator.enable) &&
+ this._automaticTransactionGeneration) {
+ this._automaticTransactionGeneration.stop().catch(() => {});
+ }
} catch (error) {
logger.error(this._logPrefix() + ' Charging station template file monitoring error: ' + error);
}
return;
}
if (interval > 0) {
- this.getConnector(connectorId).transactionSetInterval = setInterval(async () => {
+ 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);
+ await sendMeterValues(connectorId, interval, this);
} else {
- await this.sendMeterValues(connectorId, interval);
+ 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 = '') {
- // Stop heartbeat
- this._stopHeartbeat();
- // Stop the ATG
- if (Utils.convertToBoolean(this._stationInfo.AutomaticTransactionGenerator.enable) &&
- this._automaticTransactionGeneration &&
- !this._automaticTransactionGeneration.timeToStop) {
- await this._automaticTransactionGeneration.stop(reason);
- } else {
- for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
- await this.sendStopTransaction(this.getConnector(Utils.convertToInt(connector)).transactionId, reason);
- }
- }
- }
+ async stop(reason = ''): Promise<void> {
+ // Stop
+ await this._stopMessageSequence();
// eslint-disable-next-line guard-for-in
for (const connector in this._connectors) {
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) &&
this.sendBootNotification();
}
if (this._isSocketRestart) {
- this._basicStartMessageSequence();
+ this._startMessageSequence();
if (!Utils.isEmptyArray(this._messageQueue)) {
this._messageQueue.forEach((message) => {
if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) {
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;
}
if (!responseCallback) {
// Error
- throw new Error(`Response for unknown message id ${messageId}`);
+ throw new Error(`Response request for unknown message id ${messageId}`);
}
delete this._requests[messageId];
responseCallback(commandName, requestPayload);
case Constants.OCPP_JSON_CALL_ERROR_MESSAGE:
if (!this._requests[messageId]) {
// Error
- throw new Error(`Error for unknown message id ${messageId}`);
+ throw new Error(`Error request for unknown message id ${messageId}`);
}
// eslint-disable-next-line no-case-declarations
let rejectCallback;
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,
}
}
- sendStatusNotificationWithTimeout(connectorId, status, errorCode = 'NoError', timeout = Constants.STATUS_NOTIFICATION_TIMEOUT) {
- setTimeout(async () => this.sendStatusNotification(connectorId, status, errorCode), timeout);
- }
-
- async sendStartTransaction(connectorId: number, idTag?: string) {
+ async sendStartTransaction(connectorId: number, idTag?: string): Promise<unknown> {
try {
const payload = {
connectorId,
- ...!Utils.isUndefined(idTag) && { idTag },
+ ...!Utils.isUndefined(idTag) ? { idTag } : { idTag: '' },
meterStart: 0,
timestamp: new Date().toISOString(),
};
}
}
- sendStartTransactionWithTimeout(connectorId: number, idTag?: string, timeout = Constants.START_TRANSACTION_TIMEOUT) {
- setTimeout(async () => this.sendStartTransaction(connectorId, idTag), timeout);
- }
-
- async sendStopTransaction(transactionId, reason = ''): Promise<void> {
+ async sendStopTransaction(transactionId: number, reason = ''): Promise<void> {
try {
const payload = {
transactionId,
}
}
- async sendMeterValues(connectorId, interval, debug = false): Promise<void> {
+ // eslint-disable-next-line consistent-this
+ async sendMeterValues(connectorId: number, interval: number, self: ChargingStation, debug = false): Promise<void> {
try {
const sampledValues = {
timestamp: new Date().toISOString(),
sampledValue: [],
};
- const meterValuesTemplate = this.getConnector(connectorId).MeterValues;
+ const meterValuesTemplate = self.getConnector(connectorId).MeterValues;
for (let index = 0; index < meterValuesTemplate.length; index++) {
- const connector = this.getConnector(connectorId);
+ const connector = self.getConnector(connectorId);
// SoC measurand
- if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'SoC' && this._getConfigurationKey('MeterValuesSampledData').value.includes('SoC')) {
+ if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'SoC' && self._getConfigurationKey('MeterValuesSampledData').value.includes('SoC')) {
sampledValues.sampledValue.push({
...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: 'Percent' },
...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
});
const sampledValuesIndex = sampledValues.sampledValue.length - 1;
if (sampledValues.sampledValue[sampledValuesIndex].value > 100 || debug) {
- logger.error(`${this._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/100`);
+ logger.error(`${self._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/100`);
}
// Voltage measurand
- } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'Voltage' && this._getConfigurationKey('MeterValuesSampledData').value.includes('Voltage')) {
+ } 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: this._getVoltageOut() },
+ ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue },
});
- for (let phase = 1; this._getNumberOfPhases() === 3 && phase <= this._getNumberOfPhases(); phase++) {
+ for (let phase = 1; self._getNumberOfPhases() === 3 && phase <= self._getNumberOfPhases(); phase++) {
const voltageValue = sampledValues.sampledValue[sampledValues.sampledValue.length - 1].value;
let phaseValue;
if (voltageValue >= 0 && voltageValue <= 250) {
phaseValue = `L${phase}-N`;
} else if (voltageValue > 250) {
- phaseValue = `L${phase}-L${(phase + 1) % this._getNumberOfPhases() !== 0 ? (phase + 1) % this._getNumberOfPhases() : this._getNumberOfPhases()}`;
+ phaseValue = `L${phase}-L${(phase + 1) % self._getNumberOfPhases() !== 0 ? (phase + 1) % self._getNumberOfPhases() : self._getNumberOfPhases()}`;
}
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: this._getVoltageOut() },
+ ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue },
phase: phaseValue,
});
}
// Power.Active.Import measurand
- } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'Power.Active.Import' && this._getConfigurationKey('MeterValuesSampledData').value.includes('Power.Active.Import')) {
+ } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'Power.Active.Import' && self._getConfigurationKey('MeterValuesSampledData').value.includes('Power.Active.Import')) {
// FIXME: factor out powerDivider checks
- if (Utils.isUndefined(this._stationInfo.powerDivider)) {
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
+ if (Utils.isUndefined(self._stationInfo.powerDivider)) {
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
logger.error(errMsg);
throw Error(errMsg);
- } else if (this._stationInfo.powerDivider && this._stationInfo.powerDivider <= 0) {
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${this._stationInfo.powerDivider}`;
+ } else if (self._stationInfo.powerDivider && self._stationInfo.powerDivider <= 0) {
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${self._stationInfo.powerDivider}`;
logger.error(errMsg);
throw Error(errMsg);
}
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: Unknown ${this._getPowerOutType()} powerOutType in template file ${this._stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'} measurand value`;
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: Unknown ${self._getPowerOutType()} powerOutType in template file ${self._stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'} measurand value`;
const powerMeasurandValues = {} as MeasurandValues ;
- const maxPower = Math.round(this._stationInfo.maxPower / this._stationInfo.powerDivider);
- const maxPowerPerPhase = Math.round((this._stationInfo.maxPower / this._stationInfo.powerDivider) / this._getNumberOfPhases());
- switch (this._getPowerOutType()) {
+ const maxPower = Math.round(self._stationInfo.maxPower / self._stationInfo.powerDivider);
+ const maxPowerPerPhase = Math.round((self._stationInfo.maxPower / self._stationInfo.powerDivider) / self._getNumberOfPhases());
+ switch (self._getPowerOutType()) {
case 'AC':
if (Utils.isUndefined(meterValuesTemplate[index].value)) {
powerMeasurandValues.L1 = Utils.getRandomFloatRounded(maxPowerPerPhase);
powerMeasurandValues.L2 = 0;
powerMeasurandValues.L3 = 0;
- if (this._getNumberOfPhases() === 3) {
+ if (self._getNumberOfPhases() === 3) {
powerMeasurandValues.L2 = Utils.getRandomFloatRounded(maxPowerPerPhase);
powerMeasurandValues.L3 = Utils.getRandomFloatRounded(maxPowerPerPhase);
}
});
const sampledValuesIndex = sampledValues.sampledValue.length - 1;
if (sampledValues.sampledValue[sampledValuesIndex].value > maxPower || debug) {
- logger.error(`${this._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/${maxPower}`);
+ logger.error(`${self._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/${maxPower}`);
}
- for (let phase = 1; this._getNumberOfPhases() === 3 && phase <= this._getNumberOfPhases(); phase++) {
+ for (let phase = 1; self._getNumberOfPhases() === 3 && phase <= self._getNumberOfPhases(); phase++) {
const phaseValue = `L${phase}-N`;
sampledValues.sampledValue.push({
...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: 'W' },
});
}
// Current.Import measurand
- } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'Current.Import' && this._getConfigurationKey('MeterValuesSampledData').value.includes('Current.Import')) {
+ } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === 'Current.Import' && self._getConfigurationKey('MeterValuesSampledData').value.includes('Current.Import')) {
// FIXME: factor out powerDivider checks
- if (Utils.isUndefined(this._stationInfo.powerDivider)) {
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
+ if (Utils.isUndefined(self._stationInfo.powerDivider)) {
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
logger.error(errMsg);
throw Error(errMsg);
- } else if (this._stationInfo.powerDivider && this._stationInfo.powerDivider <= 0) {
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${this._stationInfo.powerDivider}`;
+ } else if (self._stationInfo.powerDivider && self._stationInfo.powerDivider <= 0) {
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${self._stationInfo.powerDivider}`;
logger.error(errMsg);
throw Error(errMsg);
}
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: Unknown ${this._getPowerOutType()} powerOutType in template file ${this._stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'} measurand value`;
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: Unknown ${self._getPowerOutType()} powerOutType in template file ${self._stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'} measurand value`;
const currentMeasurandValues = {} as MeasurandValues;
let maxAmperage;
- switch (this._getPowerOutType()) {
+ switch (self._getPowerOutType()) {
case 'AC':
- maxAmperage = ElectricUtils.ampPerPhaseFromPower(this._getNumberOfPhases(), this._stationInfo.maxPower / this._stationInfo.powerDivider, this._getVoltageOut());
+ maxAmperage = ElectricUtils.ampPerPhaseFromPower(self._getNumberOfPhases(), self._stationInfo.maxPower / self._stationInfo.powerDivider, self._getVoltageOut());
if (Utils.isUndefined(meterValuesTemplate[index].value)) {
currentMeasurandValues.L1 = Utils.getRandomFloatRounded(maxAmperage);
currentMeasurandValues.L2 = 0;
currentMeasurandValues.L3 = 0;
- if (this._getNumberOfPhases() === 3) {
+ if (self._getNumberOfPhases() === 3) {
currentMeasurandValues.L2 = Utils.getRandomFloatRounded(maxAmperage);
currentMeasurandValues.L3 = Utils.getRandomFloatRounded(maxAmperage);
}
- currentMeasurandValues.all = Utils.roundTo((currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / this._getNumberOfPhases(), 2);
+ currentMeasurandValues.all = Utils.roundTo((currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / self._getNumberOfPhases(), 2);
}
break;
case 'DC':
- maxAmperage = ElectricUtils.ampTotalFromPower(this._stationInfo.maxPower / this._stationInfo.powerDivider, this._getVoltageOut());
+ maxAmperage = ElectricUtils.ampTotalFromPower(self._stationInfo.maxPower / self._stationInfo.powerDivider, self._getVoltageOut());
if (Utils.isUndefined(meterValuesTemplate[index].value)) {
currentMeasurandValues.all = Utils.getRandomFloatRounded(maxAmperage);
}
});
const sampledValuesIndex = sampledValues.sampledValue.length - 1;
if (sampledValues.sampledValue[sampledValuesIndex].value > maxAmperage || debug) {
- logger.error(`${this._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
+ logger.error(`${self._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
}
- for (let phase = 1; this._getNumberOfPhases() === 3 && phase <= this._getNumberOfPhases(); phase++) {
+ for (let phase = 1; self._getNumberOfPhases() === 3 && phase <= self._getNumberOfPhases(); phase++) {
const phaseValue = `L${phase}`;
sampledValues.sampledValue.push({
...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: 'A' },
// Energy.Active.Import.Register measurand (default)
} else if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === 'Energy.Active.Import.Register') {
// FIXME: factor out powerDivider checks
- if (Utils.isUndefined(this._stationInfo.powerDivider)) {
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
+ if (Utils.isUndefined(self._stationInfo.powerDivider)) {
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider is undefined`;
logger.error(errMsg);
throw Error(errMsg);
- } else if (this._stationInfo.powerDivider && this._stationInfo.powerDivider <= 0) {
- const errMsg = `${this._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${this._stationInfo.powerDivider}`;
+ } else if (self._stationInfo.powerDivider && self._stationInfo.powerDivider <= 0) {
+ const errMsg = `${self._logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'}: powerDivider have zero or below value ${self._stationInfo.powerDivider}`;
logger.error(errMsg);
throw Error(errMsg);
}
if (Utils.isUndefined(meterValuesTemplate[index].value)) {
- const measurandValue = Utils.getRandomInt(this._stationInfo.maxPower / (this._stationInfo.powerDivider * 3600000) * interval);
+ const measurandValue = Utils.getRandomInt(self._stationInfo.maxPower / (self._stationInfo.powerDivider * 3600000) * interval);
// Persist previous value in connector
if (connector && !Utils.isNullOrUndefined(connector.lastEnergyActiveImportRegisterValue) && connector.lastEnergyActiveImportRegisterValue >= 0) {
connector.lastEnergyActiveImportRegisterValue += measurandValue;
...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: connector.lastEnergyActiveImportRegisterValue },
});
const sampledValuesIndex = sampledValues.sampledValue.length - 1;
- const maxConsumption = Math.round(this._stationInfo.maxPower * 3600 / (this._stationInfo.powerDivider * interval));
+ const maxConsumption = Math.round(self._stationInfo.maxPower * 3600 / (self._stationInfo.powerDivider * interval));
if (sampledValues.sampledValue[sampledValuesIndex].value > maxConsumption || debug) {
- logger.error(`${this._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/${maxConsumption}`);
+ logger.error(`${self._logPrefix()} MeterValues measurand ${sampledValues.sampledValue[sampledValuesIndex].measurand ? sampledValues.sampledValue[sampledValuesIndex].measurand : 'Energy.Active.Import.Register'}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${sampledValues.sampledValue[sampledValuesIndex].value}/${maxConsumption}`);
}
// Unsupported measurand
} else {
- logger.info(`${this._logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'} on connectorId ${connectorId}`);
+ logger.info(`${self._logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : 'Energy.Active.Import.Register'} on connectorId ${connectorId}`);
}
}
const payload = {
connectorId,
- transactionId: this.getConnector(connectorId).transactionId,
+ transactionId: self.getConnector(connectorId).transactionId,
meterValue: sampledValues,
};
- await this.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'MeterValues');
+ await self.sendMessage(Utils.generateUUID(), payload, Constants.OCPP_JSON_CALL_MESSAGE, 'MeterValues');
} catch (error) {
- logger.error(this._logPrefix() + ' Send MeterValues error: ' + error);
+ logger.error(self._logPrefix() + ' Send MeterValues error: ' + error);
throw error;
}
}
- 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(`Error ${command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR} on ${commandName}`);
- }
- // Build Message
- messageToSend = JSON.stringify([messageType, messageId, command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : '', command.details ? command.details : {}]);
+ // Build Error Message
+ 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(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 : {}));
}
- // Request?
- if (messageType !== Constants.OCPP_JSON_CALL_MESSAGE) {
+ // Response?
+ if (messageType === Constants.OCPP_JSON_CALL_RESULT_MESSAGE) {
// Yes: send Ok
resolve();
- } else if (this._wsConnection && this._wsConnection.readyState === WebSocket.OPEN) {
- // Send timeout in case connection is open otherwise wait for ever
- // FIXME: Handle message on timeout
- setTimeout(() => rejectCallback(new OCPPError(command.code ? command.code : Constants.OCPP_ERROR_GENERIC_ERROR, command.message ? command.message : '', command.details ? command.details : {})), Constants.OCPP_SOCKET_TIMEOUT);
+ } else if (messageType === Constants.OCPP_JSON_CALL_ERROR_MESSAGE) {
+ // Send 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) {
- self.handleResponse(commandName, 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(`Error on commandName ${commandName}`, 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);
this._heartbeatInterval = payload.interval * 1000;
this._addConfigurationKey('HeartBeatInterval', Utils.convertToInt(payload.interval));
this._addConfigurationKey('HeartbeatInterval', Utils.convertToInt(payload.interval), false, false);
- this._basicStartMessageSequence();
+ this._startMessageSequence();
} else if (payload.status === 'Pending') {
logger.info(this._logPrefix() + ' Charging station in pending state on the central server');
} else {
}
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.sendStartTransactionWithTimeout(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.sendStartTransactionWithTimeout(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;
}
}