import { AuthorizationStatus, StartTransactionRequest, StartTransactionResponse, StopTransactionReason, StopTransactionRequest, StopTransactionResponse } from '../types/ocpp/1.6/Transaction';
-import { BootNotificationResponse, ChangeConfigurationResponse, DefaultResponse, GetConfigurationResponse, HeartbeatResponse, RegistrationStatus, StatusNotificationResponse, UnlockConnectorResponse } from '../types/ocpp/1.6/RequestResponses';
+import { BootNotificationResponse, ChangeConfigurationResponse, DefaultResponse, GetConfigurationResponse, HeartbeatResponse, RegistrationStatus, SetChargingProfileResponse, StatusNotificationResponse, UnlockConnectorResponse } from '../types/ocpp/1.6/RequestResponses';
+import { ChargingProfile, ChargingProfilePurposeType } from '../types/ocpp/1.6/ChargingProfile';
import ChargingStationConfiguration, { ConfigurationKey } from '../types/ChargingStationConfiguration';
import ChargingStationTemplate, { PowerOutType } from '../types/ChargingStationTemplate';
import Connectors, { Connector } from '../types/Connectors';
import { MeterValue, MeterValueLocation, MeterValueMeasurand, MeterValuePhase, MeterValueUnit, MeterValuesRequest, MeterValuesResponse, SampledValue } from '../types/ocpp/1.6/MeterValues';
import { PerformanceObserver, performance } from 'perf_hooks';
-import Requests, { BootNotificationRequest, ChangeConfigurationRequest, GetConfigurationRequest, HeartbeatRequest, RemoteStartTransactionRequest, RemoteStopTransactionRequest, ResetRequest, StatusNotificationRequest, UnlockConnectorRequest } from '../types/ocpp/1.6/Requests';
+import Requests, { BootNotificationRequest, ChangeConfigurationRequest, GetConfigurationRequest, HeartbeatRequest, RemoteStartTransactionRequest, RemoteStopTransactionRequest, ResetRequest, SetChargingProfileRequest, StatusNotificationRequest, UnlockConnectorRequest } from '../types/ocpp/1.6/Requests';
import WebSocket, { MessageEvent } from 'ws';
import AutomaticTransactionGenerator from './AutomaticTransactionGenerator';
}
const templateMaxConnectors = this._getTemplateMaxNumberOfConnectors();
if (templateMaxConnectors <= 0) {
- logger.warn(`${this._logPrefix()} Charging station template ${this._stationTemplateFile} with no connector configurations`);
+ logger.warn(`${this._logPrefix()} Charging station template ${this._stationTemplateFile} with no connector configuration`);
+ }
+ if (!this._stationInfo.Connectors[0]) {
+ logger.warn(`${this._logPrefix()} Charging station template ${this._stationTemplateFile} with no connector Id 0 configuration`);
}
// Sanity check
if (maxConnectors > (this._stationInfo.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) && !this._stationInfo.randomConnectors) {
// Add connector Id 0
let lastConnector = '0';
for (lastConnector in this._stationInfo.Connectors) {
- if (Utils.convertToInt(lastConnector) === 0 && this._stationInfo.useConnectorId0 && this._stationInfo.Connectors[lastConnector]) {
+ if (Utils.convertToInt(lastConnector) === 0 && this._getUseConnectorId0() && this._stationInfo.Connectors[lastConnector]) {
this._connectors[lastConnector] = Utils.cloneObject(this._stationInfo.Connectors[lastConnector]) as Connector;
}
}
delete this._stationInfo.Connectors;
// Initialize transaction attributes on connectors
for (const connector in this._connectors) {
- if (!this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ if (Utils.convertToInt(connector) > 0 && !this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
this._initTransactionOnConnector(Utils.convertToInt(connector));
}
}
return this._stationInfo.authorizationFile && this._stationInfo.authorizationFile;
}
+ _getUseConnectorId0(): boolean {
+ return !Utils.isUndefined(this._stationInfo.useConnectorId0) ? this._stationInfo.useConnectorId0 : true;
+ }
+
_loadAndGetAuthorizedTags(): string[] {
let authorizedTags: string[] = [];
const authorizationFile = this._getAuthorizationFile();
_getNumberOfPhases(): number {
switch (this._getPowerOutType()) {
case PowerOutType.AC:
- return !Utils.isUndefined(this._stationInfo.numberOfPhases) ? Utils.convertToInt(this._stationInfo.numberOfPhases) : 3;
+ return !Utils.isUndefined(this._stationInfo.numberOfPhases) ? this._stationInfo.numberOfPhases : 3;
case PowerOutType.DC:
return 0;
}
_getNumberOfRunningTransactions(): number {
let trxCount = 0;
for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
trxCount++;
}
}
logger.error(errMsg);
throw Error(errMsg);
}
- return !Utils.isUndefined(this._stationInfo.voltageOut) ? Utils.convertToInt(this._stationInfo.voltageOut) : defaultVoltageOut;
+ return !Utils.isUndefined(this._stationInfo.voltageOut) ? this._stationInfo.voltageOut : defaultVoltageOut;
}
_getTransactionIdTag(transactionId: number): string {
for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
return this.getConnector(Utils.convertToInt(connector)).idTag;
}
}
_getTransactionMeterStop(transactionId: number): number {
for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
return this.getConnector(Utils.convertToInt(connector)).lastEnergyActiveImportRegisterValue;
}
}
this._startHeartbeat();
// Initialize connectors status
for (const connector in this._connectors) {
- if (!this._hasStopped && !this.getConnector(Utils.convertToInt(connector)).status && this.getConnector(Utils.convertToInt(connector)).bootStatus) {
+ if (Utils.convertToInt(connector) === 0) {
+ continue;
+ } else if (!this._hasStopped && !this.getConnector(Utils.convertToInt(connector)).status && this.getConnector(Utils.convertToInt(connector)).bootStatus) {
// Send status in template at startup
await this.sendStatusNotification(Utils.convertToInt(connector), this.getConnector(Utils.convertToInt(connector)).bootStatus);
} else if (this._hasStopped && this.getConnector(Utils.convertToInt(connector)).bootStatus) {
await this._automaticTransactionGeneration.stop(reason);
} else {
for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionStarted) {
await this.sendStopTransaction(this.getConnector(Utils.convertToInt(connector)).transactionId, reason);
}
}
}
}
- _openWSConnection(options?: WebSocket.ClientOptions): void {
+ _openWSConnection(options?: WebSocket.ClientOptions, forceCloseOpened = false): void {
if (Utils.isUndefined(options)) {
options = {} as WebSocket.ClientOptions;
}
if (Utils.isUndefined(options.handshakeTimeout)) {
options.handshakeTimeout = this._connectionTimeout;
}
+ if (this._wsConnection?.readyState === WebSocket.OPEN && forceCloseOpened) {
+ this._wsConnection.close();
+ }
this._wsConnection = new WebSocket(this._wsConnectionUrl, 'ocpp' + Constants.OCPP_VERSION_16, options);
logger.info(this._logPrefix() + ' Will communicate through URL ' + this._supervisionUrl);
}
async stop(reason: StopTransactionReason = StopTransactionReason.NONE): Promise<void> {
// Stop message sequence
await this._stopMessageSequence(reason);
- // eslint-disable-next-line guard-for-in
for (const connector in this._connectors) {
- await this.sendStatusNotification(Utils.convertToInt(connector), ChargePointStatus.UNAVAILABLE);
+ if (Utils.convertToInt(connector) > 0) {
+ await this.sendStatusNotification(Utils.convertToInt(connector), ChargePointStatus.UNAVAILABLE);
+ }
}
if (this._wsConnection?.readyState === WebSocket.OPEN) {
this._wsConnection.close();
await Utils.sleep(reconnectDelay);
logger.error(this._logPrefix() + ' Socket: reconnecting try #' + this._autoReconnectRetryCount.toString());
this._openWSConnection({ handshakeTimeout: reconnectDelay - 100 });
+ this._hasSocketRestarted = true;
} else if (this._autoReconnectMaxRetries !== -1) {
logger.error(`${this._logPrefix()} Socket: max retries reached (${this._autoReconnectRetryCount}) or retry disabled (${this._autoReconnectMaxRetries})`);
}
async onError(errorEvent): Promise<void> {
switch (errorEvent.code) {
case 'ECONNREFUSED':
- this._hasSocketRestarted = true;
await this._reconnect(errorEvent);
break;
default:
this._autoReconnectRetryCount = 0;
break;
default: // Abnormal close
- this._hasSocketRestarted = true;
await this._reconnect(closeEvent);
break;
}
...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue.toString() },
});
for (let phase = 1; self._getNumberOfPhases() === 3 && phase <= self._getNumberOfPhases(); phase++) {
- const voltageValue = Utils.convertToFloat(meterValue.sampledValue[meterValue.sampledValue.length - 1].value);
let phaseValue: string;
- if (voltageValue >= 0 && voltageValue <= 250) {
+ if (self._getVoltageOut() >= 0 && self._getVoltageOut() <= 250) {
phaseValue = `L${phase}-N`;
- } else if (voltageValue > 250) {
+ } else if (self._getVoltageOut() > 250) {
phaseValue = `L${phase}-L${(phase + 1) % self._getNumberOfPhases() !== 0 ? (phase + 1) % self._getNumberOfPhases() : self._getNumberOfPhases()}`;
}
meterValue.sampledValue.push({
}
// Function that will receive the request's response
- function responseCallback(payload, requestPayload): void {
+ async function responseCallback(payload, requestPayload): Promise<void> {
if (self.getEnableStatistics()) {
self._statistics.addMessage(commandName, messageType);
}
// Send the response
- self.handleResponse(commandName, payload, requestPayload);
+ await self.handleResponse(commandName, payload, requestPayload);
resolve(payload);
}
});
}
- handleResponse(commandName: string, payload, requestPayload): void {
+ async handleResponse(commandName: string, payload, requestPayload): Promise<void> {
const responseCallbackFn = 'handleResponse' + commandName;
if (typeof this[responseCallbackFn] === 'function') {
- this[responseCallbackFn](payload, requestPayload);
+ await this[responseCallbackFn](payload, requestPayload);
} else {
logger.error(this._logPrefix() + ' Trying to call an undefined response callback function: ' + responseCallbackFn);
}
handleResponseBootNotification(payload: BootNotificationResponse, requestPayload: BootNotificationRequest): void {
if (payload.status === RegistrationStatus.ACCEPTED) {
- this._heartbeatInterval = Utils.convertToInt(payload.interval) * 1000;
+ this._heartbeatInterval = payload.interval * 1000;
this._heartbeatSetInterval ? this._restartHeartbeat() : this._startHeartbeat();
this._addConfigurationKey('HeartBeatInterval', payload.interval.toString());
this._addConfigurationKey('HeartbeatInterval', payload.interval.toString(), false, false);
}
}
- handleResponseStartTransaction(payload: StartTransactionResponse, requestPayload: StartTransactionRequest): void {
- const connectorId = Utils.convertToInt(requestPayload.connectorId);
+ async handleResponseStartTransaction(payload: StartTransactionResponse, requestPayload: StartTransactionRequest): Promise<void> {
+ const connectorId = requestPayload.connectorId;
if (this.getConnector(connectorId).transactionStarted) {
logger.debug(this._logPrefix() + ' Trying to start a transaction on an already used connector ' + connectorId.toString() + ': %j', this.getConnector(connectorId));
return;
let transactionConnectorId: number;
for (const connector in this._connectors) {
- if (Utils.convertToInt(connector) === connectorId) {
+ if (Utils.convertToInt(connector) > 0 && Utils.convertToInt(connector) === connectorId) {
transactionConnectorId = Utils.convertToInt(connector);
break;
}
this.getConnector(connectorId).transactionId = payload.transactionId;
this.getConnector(connectorId).idTag = requestPayload.idTag;
this.getConnector(connectorId).lastEnergyActiveImportRegisterValue = 0;
- this.sendStatusNotification(connectorId, ChargePointStatus.CHARGING).catch(() => { });
+ await this.sendStatusNotification(connectorId, ChargePointStatus.CHARGING);
logger.info(this._logPrefix() + ' Transaction ' + payload.transactionId.toString() + ' STARTED on ' + this._stationInfo.name + '#' + connectorId.toString() + ' for idTag ' + requestPayload.idTag);
if (this._stationInfo.powerSharedByConnectors) {
this._stationInfo.powerDivider++;
} else {
logger.error(this._logPrefix() + ' Starting transaction id ' + payload.transactionId.toString() + ' REJECTED with status ' + payload.idTagInfo.status + ', idTag ' + requestPayload.idTag);
this._resetTransactionOnConnector(connectorId);
- this.sendStatusNotification(connectorId, ChargePointStatus.AVAILABLE).catch(() => { });
+ await this.sendStatusNotification(connectorId, ChargePointStatus.AVAILABLE);
}
}
- handleResponseStopTransaction(payload: StopTransactionResponse, requestPayload: StopTransactionRequest): void {
+ async handleResponseStopTransaction(payload: StopTransactionResponse, requestPayload: StopTransactionRequest): Promise<void> {
let transactionConnectorId: number;
for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionId === Utils.convertToInt(requestPayload.transactionId)) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionId === requestPayload.transactionId) {
transactionConnectorId = Utils.convertToInt(connector);
break;
}
return;
}
if (payload.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
- this.sendStatusNotification(transactionConnectorId, ChargePointStatus.AVAILABLE).catch(() => { });
+ await this.sendStatusNotification(transactionConnectorId, ChargePointStatus.AVAILABLE);
if (this._stationInfo.powerSharedByConnectors) {
this._stationInfo.powerDivider--;
}
}
async handleRequestUnlockConnector(commandPayload: UnlockConnectorRequest): Promise<UnlockConnectorResponse> {
- const connectorId = Utils.convertToInt(commandPayload.connectorId);
+ const connectorId = commandPayload.connectorId;
if (connectorId === 0) {
logger.error(this._logPrefix() + ' Trying to unlock connector ' + connectorId.toString());
return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
}
handleRequestChangeConfiguration(commandPayload: ChangeConfigurationRequest): ChangeConfigurationResponse {
+ // JSON request fields type sanity check
+ if (!Utils.isString(commandPayload.key)) {
+ logger.error(`${this._logPrefix()} ChangeConfiguration request key field is not a string:`, commandPayload);
+ }
+ if (!Utils.isString(commandPayload.value)) {
+ logger.error(`${this._logPrefix()} ChangeConfiguration request value field is not a string:`, commandPayload);
+ }
const keyToChange = this._getConfigurationKey(commandPayload.key);
if (!keyToChange) {
return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
}
}
+ handleRequestSetChargingProfile(commandPayload: SetChargingProfileRequest): SetChargingProfileResponse {
+ if (!this.getConnector(commandPayload.connectorId)) {
+ logger.error(`${this._logPrefix()} Trying to set a charging profile to a non existing connector Id ${commandPayload.connectorId}`);
+ return Constants.OCPP_CHARGING_PROFILE_RESPONSE_REJECTED;
+ }
+ if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE && !this.getConnector(commandPayload.connectorId)?.transactionStarted) {
+ return Constants.OCPP_CHARGING_PROFILE_RESPONSE_REJECTED;
+ }
+ this.getConnector(commandPayload.connectorId).chargingProfiles.forEach((chargingProfile: ChargingProfile, index: number) => {
+ if (chargingProfile.chargingProfileId === commandPayload.csChargingProfiles.chargingProfileId
+ || (chargingProfile.stackLevel === commandPayload.csChargingProfiles.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.csChargingProfiles.chargingProfilePurpose)) {
+ this.getConnector(commandPayload.connectorId).chargingProfiles[index] = chargingProfile;
+ return Constants.OCPP_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+ }
+ });
+ this.getConnector(commandPayload.connectorId).chargingProfiles.push(commandPayload.csChargingProfiles);
+ return Constants.OCPP_CHARGING_PROFILE_RESPONSE_ACCEPTED;
+ }
+
async handleRequestRemoteStartTransaction(commandPayload: RemoteStartTransactionRequest): Promise<DefaultResponse> {
- const transactionConnectorID: number = commandPayload.connectorId ? Utils.convertToInt(commandPayload.connectorId) : 1;
+ const transactionConnectorID: number = commandPayload.connectorId ? commandPayload.connectorId : 1;
if (this._getAuthorizeRemoteTxRequests() && this._getLocalAuthListEnabled() && this.hasAuthorizedTags()) {
// Check if authorized
if (this._authorizedTags.find((value) => value === commandPayload.idTag)) {
}
async handleRequestRemoteStopTransaction(commandPayload: RemoteStopTransactionRequest): Promise<DefaultResponse> {
- const transactionId = Utils.convertToInt(commandPayload.transactionId);
+ const transactionId = commandPayload.transactionId;
for (const connector in this._connectors) {
- if (this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
+ if (Utils.convertToInt(connector) > 0 && this.getConnector(Utils.convertToInt(connector)).transactionId === transactionId) {
await this.sendStopTransaction(transactionId);
return Constants.OCPP_RESPONSE_ACCEPTED;
}