-// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
import crypto from 'crypto';
import fs from 'fs';
import { URL } from 'url';
import { parentPort } from 'worker_threads';
-import WebSocket, { Data, RawData } from 'ws';
+import WebSocket, { type RawData } from 'ws';
import BaseError from '../exception/BaseError';
import OCPPError from '../exception/OCPPError';
import { OCPPVersion } from '../types/ocpp/OCPPVersion';
import {
AvailabilityType,
- BootNotificationRequest,
- CachedRequest,
- HeartbeatRequest,
- IncomingRequest,
+ type BootNotificationRequest,
+ type CachedRequest,
+ type ErrorCallback,
+ type HeartbeatRequest,
+ type IncomingRequest,
IncomingRequestCommand,
- MeterValuesRequest,
+ type MeterValuesRequest,
RequestCommand,
- StatusNotificationRequest,
+ type ResponseCallback,
+ type StatusNotificationRequest,
} from '../types/ocpp/Requests';
import {
- BootNotificationResponse,
- ErrorResponse,
- HeartbeatResponse,
- MeterValuesResponse,
+ type BootNotificationResponse,
+ type ErrorResponse,
+ type HeartbeatResponse,
+ type MeterValuesResponse,
RegistrationStatus,
- Response,
- StatusNotificationResponse,
+ type Response,
+ type StatusNotificationResponse,
} from '../types/ocpp/Responses';
import {
StopTransactionReason,
- StopTransactionRequest,
- StopTransactionResponse,
+ type StopTransactionRequest,
+ type StopTransactionResponse,
} from '../types/ocpp/Transaction';
import { WSError, WebSocketCloseEventStatusCode } from '../types/WebSocket';
import Configuration from '../utils/Configuration';
import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService';
import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
+import OCPP20IncomingRequestService from './ocpp/2.0/OCPP20IncomingRequestService';
+import OCPP20RequestService from './ocpp/2.0/OCPP20RequestService';
+import OCPP20ResponseService from './ocpp/2.0/OCPP20ResponseService';
import type OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
import type OCPPRequestService from './ocpp/OCPPRequestService';
import SharedLRUCache from './SharedLRUCache';
public readonly templateFile: string;
public stationInfo!: ChargingStationInfo;
public started: boolean;
+ public starting: boolean;
public authorizedTagsCache: AuthorizedTagsCache;
public automaticTransactionGenerator!: AutomaticTransactionGenerator;
public ocppConfiguration!: ChargingStationOcppConfiguration;
public bootNotificationRequest!: BootNotificationRequest;
public bootNotificationResponse!: BootNotificationResponse | null;
public powerDivider!: number;
- private starting: boolean;
private stopping: boolean;
private configurationFile!: string;
private configurationFileHash!: string;
options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
params.closeOpened = params?.closeOpened ?? false;
params.terminateOpened = params?.terminateOpened ?? false;
+ if (this.started === false && this.starting === false) {
+ logger.warn(
+ `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()} on stopped charging station`
+ );
+ return;
+ }
if (
!Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
!Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
if (params?.terminateOpened) {
this.terminateWSConnection();
}
+ const ocppVersion = this.getOcppVersion();
let protocol: string;
- switch (this.getOcppVersion()) {
+ switch (ocppVersion) {
case OCPPVersion.VERSION_16:
- protocol = 'ocpp' + OCPPVersion.VERSION_16;
+ case OCPPVersion.VERSION_20:
+ case OCPPVersion.VERSION_201:
+ protocol = 'ocpp' + ocppVersion;
break;
default:
- this.handleUnsupportedVersion(this.getOcppVersion());
+ this.handleUnsupportedVersion(ocppVersion);
break;
}
private getStationInfoFromTemplate(): ChargingStationInfo {
const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile();
if (Utils.isNullOrUndefined(stationTemplate)) {
- const errorMsg = 'Failed to read charging station template file';
+ const errorMsg = `Failed to read charging station template file ${this.templateFile}`;
logger.error(`${this.logPrefix()} ${errorMsg}`);
throw new BaseError(errorMsg);
}
'supervisionUrl',
'supervisionUrls'
);
+ const firmwareVersionRegExp = stationTemplate.firmwareVersionPattern
+ ? new RegExp(stationTemplate.firmwareVersionPattern)
+ : Constants.SEMVER_REGEXP;
+ if (
+ stationTemplate.firmwareVersion &&
+ firmwareVersionRegExp.test(stationTemplate.firmwareVersion) === false
+ ) {
+ logger.warn(
+ `${this.logPrefix()} Firmware version '${
+ stationTemplate.firmwareVersion
+ }' in template file ${
+ this.templateFile
+ } does not match regular expression '${firmwareVersionRegExp.toString()}'`
+ );
+ }
const stationInfo: ChargingStationInfo =
ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
OCPP16ResponseService.getInstance<OCPP16ResponseService>()
);
break;
+ case OCPPVersion.VERSION_20:
+ case OCPPVersion.VERSION_201:
+ this.ocppIncomingRequestService =
+ OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>();
+ this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
+ OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+ );
+ break;
default:
this.handleUnsupportedVersion(this.getOcppVersion());
break;
}
// Initialize transaction attributes on connectors
for (const connectorId of this.connectors.keys()) {
+ if (connectorId > 0 && this.getConnectorStatus(connectorId).transactionStarted === true) {
+ logger.warn(
+ `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
+ this.getConnectorStatus(connectorId).transactionId
+ }`
+ );
+ }
if (
connectorId > 0 &&
(this.getConnectorStatus(connectorId).transactionStarted === undefined ||
- this.getConnectorStatus(connectorId).transactionStarted === false)
+ this.getConnectorStatus(connectorId).transactionStarted === null)
) {
this.initializeConnectorStatus(connectorId);
}
private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | null {
let configuration: ChargingStationConfiguration = null;
- if (this.getOcppPersistentConfiguration()) {
+ if (this.getOcppPersistentConfiguration() === true) {
const configurationFromFile = this.getConfigurationFromFile();
configuration = configurationFromFile?.configurationKey && configurationFromFile;
}
);
}
if (this.isRegistered() === true) {
- if (this.isInAcceptedState()) {
+ if (this.isInAcceptedState() === true) {
await this.startMessageSequence();
}
} else {
}
}
- private async onClose(code: number, reason: string): Promise<void> {
+ private async onClose(code: number, reason: Buffer): Promise<void> {
switch (code) {
// Normal close
case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
logger.info(
`${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
code
- )}' and reason '${reason}'`
+ )}' and reason '${reason.toString()}'`
);
this.autoReconnectRetryCount = 0;
break;
logger.error(
`${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
code
- )}' and reason '${reason}'`
+ )}' and reason '${reason.toString()}'`
);
this.started === true && (await this.reconnect());
break;
parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
}
- private async onMessage(data: Data): Promise<void> {
+ private async onMessage(data: RawData): Promise<void> {
let messageType: number;
let messageId: string;
let commandName: IncomingRequestCommand;
let errorType: ErrorType;
let errorMessage: string;
let errorDetails: JsonType;
- let responseCallback: (payload: JsonType, requestPayload: JsonType) => void;
- let errorCallback: (error: OCPPError, requestStatistic?: boolean) => void;
+ let responseCallback: ResponseCallback;
+ let errorCallback: ErrorCallback;
let requestCommandName: RequestCommand | IncomingRequestCommand;
let requestPayload: JsonType;
let cachedRequest: CachedRequest;
if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
this.webSocketPingSetInterval = setInterval(() => {
if (this.isWebSocketConnectionOpened() === true) {
- this.wsConnection.ping((): void => {
- /* This is intentional */
- });
+ this.wsConnection.ping();
}
}, webSocketPingInterval * 1000);
logger.info(
}
private getConfiguredSupervisionUrl(): URL {
- const supervisionUrls = Utils.cloneObject<string | string[]>(
- this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls()
- );
+ const supervisionUrls = this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls();
if (!Utils.isEmptyArray(supervisionUrls)) {
switch (Configuration.getSupervisionUrlDistribution()) {
case SupervisionUrlDistribution.ROUND_ROBIN: