import WebSocket, { type RawData } from 'ws';
import {
- AuthorizedTagsCache,
AutomaticTransactionGenerator,
ChargingStationConfigurationUtils,
ChargingStationUtils,
ChargingStationWorkerBroadcastChannel,
+ IdTagsCache,
MessageChannelUtils,
SharedLRUCache,
} from './internal';
type ErrorCallback,
type ErrorResponse,
ErrorType,
+ type EvseStatus,
FileType,
FirmwareStatus,
type FirmwareStatusNotificationRequest,
public stationInfo!: ChargingStationInfo;
public started: boolean;
public starting: boolean;
- public authorizedTagsCache: AuthorizedTagsCache;
+ public idTagsCache: IdTagsCache;
public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
public wsConnection!: WebSocket | null;
public readonly connectors: Map<number, ConnectorStatus>;
+ public readonly evses: Map<number, EvseStatus>;
public readonly requests: Map<string, CachedRequest>;
public performanceStatistics!: PerformanceStatistics | undefined;
public heartbeatSetInterval!: NodeJS.Timeout;
private configurationFile!: string;
private configurationFileHash!: string;
private connectorsConfigurationHash!: string;
+ private evsesConfigurationHash!: string;
private ocppIncomingRequestService!: OCPPIncomingRequestService;
private readonly messageBuffer: Set<string>;
private configuredSupervisionUrl!: URL;
this.index = index;
this.templateFile = templateFile;
this.connectors = new Map<number, ConnectorStatus>();
+ this.evses = new Map<number, EvseStatus>();
this.requests = new Map<string, CachedRequest>();
this.messageBuffer = new Set<string>();
this.sharedLRUCache = SharedLRUCache.getInstance();
- this.authorizedTagsCache = AuthorizedTagsCache.getInstance();
+ this.idTagsCache = IdTagsCache.getInstance();
this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
this.initialize();
public logPrefix = (): string => {
return Utils.logPrefix(
` ${
- (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId) &&
- this?.stationInfo?.chargingStationId) ??
- ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile()) ??
- ''
+ (Utils.isNotEmptyString(this?.stationInfo?.chargingStationId)
+ ? this?.stationInfo?.chargingStationId
+ : ChargingStationUtils.getChargingStationId(this.index, this.getTemplateFromFile())) ??
+ 'Error at building log prefix'
} |`
);
};
- public hasAuthorizedTags(): boolean {
- return Utils.isNotEmptyArray(
- this.authorizedTagsCache.getAuthorizedTags(
- ChargingStationUtils.getAuthorizationFile(this.stationInfo)
- )
- );
+ public hasIdTags(): boolean {
+ const idTagsFile = ChargingStationUtils.getIdTagsFile(this.stationInfo);
+ return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile));
}
public getEnableStatistics(): boolean {
}
public isChargingStationAvailable(): boolean {
- return this.getConnectorStatus(0)?.availability === AvailabilityType.OPERATIVE;
+ return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative;
}
public isConnectorAvailable(id: number): boolean {
- return id > 0 && this.getConnectorStatus(id)?.availability === AvailabilityType.OPERATIVE;
+ return id > 0 && this.getConnectorStatus(id)?.availability === AvailabilityType.Operative;
}
public getNumberOfConnectors(): number {
public startMeterValues(connectorId: number, interval: number): void {
if (connectorId === 0) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId.toString()}`
+ `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()}`
);
return;
}
if (!this.getConnectorStatus(connectorId)) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on non existing connector Id ${connectorId.toString()}`
+ `${this.logPrefix()} Trying to start MeterValues on non existing connector id ${connectorId.toString()}`
);
return;
}
if (this.getConnectorStatus(connectorId)?.transactionStarted === false) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction started`
+ `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started`
);
return;
} else if (
Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId)
) {
logger.error(
- `${this.logPrefix()} Trying to start MeterValues on connector Id ${connectorId} with no transaction id`
+ `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id`
);
return;
}
private flushMessageBuffer(): void {
if (this.messageBuffer.size > 0) {
- this.messageBuffer.forEach((message) => {
+ for (const message of this.messageBuffer.values()) {
let beginId: string;
let commandName: RequestCommand;
const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse;
)} payload sent: ${message}`
);
this.messageBuffer.delete(message);
- });
+ }
}
}
logger.error(`${this.logPrefix()} ${errorMsg}`);
throw new BaseError(errorMsg);
}
- // Deprecation template keys section
- ChargingStationUtils.warnDeprecatedTemplateKey(
- stationTemplate,
- 'supervisionUrl',
+ ChargingStationUtils.warnTemplateKeysDeprecation(
this.templateFile,
- this.logPrefix(),
- "Use 'supervisionUrls' instead"
- );
- ChargingStationUtils.convertDeprecatedTemplateKey(
stationTemplate,
- 'supervisionUrl',
- 'supervisionUrls'
+ this.logPrefix()
);
const stationInfo: ChargingStationInfo =
ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
this.templateFile,
this.logPrefix()
);
- const templateMaxConnectors =
- ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate);
- ChargingStationUtils.checkTemplateMaxConnectors(
- templateMaxConnectors,
- this.templateFile,
- this.logPrefix()
- );
- if (
- configuredMaxConnectors >
- (stationTemplate?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) &&
- !stationTemplate?.randomConnectors
- ) {
- logger.warn(
- `${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${
- this.templateFile
- }, forcing random connector configurations affectation`
+ // Build evses or connectors if needed (FIXME: should be factored out)
+ if (stationInfo?.Connectors && !stationInfo?.Evses) {
+ const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors(
+ stationTemplate.Connectors
);
- stationInfo.randomConnectors = true;
+ ChargingStationUtils.checkTemplateMaxConnectors(
+ templateMaxConnectors,
+ this.templateFile,
+ this.logPrefix()
+ );
+ if (
+ configuredMaxConnectors >
+ (stationTemplate?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) &&
+ !stationTemplate?.randomConnectors
+ ) {
+ logger.warn(
+ `${this.logPrefix()} Number of connectors exceeds the number of connector configurations in template ${
+ this.templateFile
+ }, forcing random connector configurations affectation`
+ );
+ stationInfo.randomConnectors = true;
+ }
+ this.initializeConnectors(stationInfo, configuredMaxConnectors);
+ } else if (stationInfo?.Evses && !stationInfo?.Connectors) {
+ this.initializeEvses(stationInfo);
+ } else if (stationInfo?.Evses && stationInfo?.Connectors) {
+ const errorMsg = `Connectors and evses defined at the same time in template file ${this.templateFile}`;
+ logger.error(`${this.logPrefix()} ${errorMsg}`);
+ throw new BaseError(errorMsg);
+ } else {
+ const errorMsg = `No connectors or evses defined in template file ${this.templateFile}`;
+ logger.error(`${this.logPrefix()} ${errorMsg}`);
+ throw new BaseError(errorMsg);
}
- // Build connectors if needed (FIXME: should be factored out)
- this.initializeConnectors(stationInfo, configuredMaxConnectors, templateMaxConnectors);
stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
ChargingStationUtils.createStationInfoHash(stationInfo);
return stationInfo;
private getStationInfo(): ChargingStationInfo {
const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate();
const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile();
- // Priority: charging station info from template > charging station info from configuration file > charging station info attribute
+ // Priority:
+ // 1. charging station info from template
+ // 2. charging station info from configuration file
+ // 3. charging station info attribute
if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) {
if (this.stationInfo?.infoHash === stationInfoFromFile?.infoHash) {
return this.stationInfo;
`${ChargingStationUtils.getHashId(this.index, this.getTemplateFromFile())}.json`
);
this.stationInfo = this.getStationInfo();
+ if (
+ this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
+ Utils.isNotEmptyString(this.stationInfo.firmwareVersion) &&
+ Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern)
+ ) {
+ const patternGroup: number | undefined =
+ this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
+ this.stationInfo.firmwareVersion?.split('.').length;
+ const match = this.stationInfo?.firmwareVersion
+ ?.match(new RegExp(this.stationInfo.firmwareVersionPattern))
+ ?.slice(1, patternGroup + 1);
+ const patchLevelIndex = match.length - 1;
+ match[patchLevelIndex] = (
+ Utils.convertToInt(match[patchLevelIndex]) +
+ this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
+ ).toString();
+ this.stationInfo.firmwareVersion = match?.join('.');
+ }
this.saveStationInfo();
// Avoid duplication of connectors related information in RAM
delete this.stationInfo?.Connectors;
status: RegistrationStatusEnumType.ACCEPTED,
};
}
- if (
- this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
- Utils.isNotEmptyString(this.stationInfo.firmwareVersion) &&
- Utils.isNotEmptyString(this.stationInfo.firmwareVersionPattern)
- ) {
- const patternGroup: number | undefined =
- this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
- this.stationInfo.firmwareVersion?.split('.').length;
- const match = this.stationInfo?.firmwareVersion
- ?.match(new RegExp(this.stationInfo.firmwareVersionPattern))
- ?.slice(1, patternGroup + 1);
- const patchLevelIndex = match.length - 1;
- match[patchLevelIndex] = (
- Utils.convertToInt(match[patchLevelIndex]) +
- this.stationInfo.firmwareUpgrade?.versionUpgrade?.step
- ).toString();
- this.stationInfo.firmwareVersion = match?.join('.');
- }
}
private initializeOcppServices(): void {
private initializeConnectors(
stationInfo: ChargingStationInfo,
- configuredMaxConnectors: number,
- templateMaxConnectors: number
+ configuredMaxConnectors: number
): void {
if (!stationInfo?.Connectors && this.connectors.size === 0) {
const logMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`;
logger.warn(
`${this.logPrefix()} Charging station information from template ${
this.templateFile
- } with no connector Id 0 configuration`
+ } with no connector id 0 configuration`
);
}
if (stationInfo?.Connectors) {
if (this.connectors?.size === 0 || connectorsConfigChanged) {
connectorsConfigChanged && this.connectors.clear();
this.connectorsConfigurationHash = connectorsConfigHash;
- // Add connector Id 0
+ // Add connector id 0
let lastConnector = '0';
for (lastConnector in stationInfo?.Connectors) {
const connectorStatus = stationInfo?.Connectors[lastConnector];
lastConnectorId,
Utils.cloneObject<ConnectorStatus>(connectorStatus)
);
- this.getConnectorStatus(lastConnectorId).availability = AvailabilityType.OPERATIVE;
+ this.getConnectorStatus(lastConnectorId).availability = AvailabilityType.Operative;
if (Utils.isUndefined(this.getConnectorStatus(lastConnectorId)?.chargingProfiles)) {
this.getConnectorStatus(lastConnectorId).chargingProfiles = [];
}
}
}
// Generate all connectors
+ const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors(
+ stationInfo?.Connectors
+ );
if ((stationInfo?.Connectors[0] ? templateMaxConnectors - 1 : templateMaxConnectors) > 0) {
for (let index = 1; index <= configuredMaxConnectors; index++) {
const randConnectorId = stationInfo?.randomConnectors
const connectorStatus = stationInfo?.Connectors[randConnectorId.toString()];
this.checkStationInfoConnectorStatus(randConnectorId, connectorStatus);
this.connectors.set(index, Utils.cloneObject<ConnectorStatus>(connectorStatus));
- this.getConnectorStatus(index).availability = AvailabilityType.OPERATIVE;
+ this.getConnectorStatus(index).availability = AvailabilityType.Operative;
if (Utils.isUndefined(this.getConnectorStatus(index)?.chargingProfiles)) {
this.getConnectorStatus(index).chargingProfiles = [];
}
} with no connectors configuration defined, using already defined connectors`
);
}
- // Initialize transaction attributes on connectors
+ // Initialize connectors status
for (const connectorId of this.connectors.keys()) {
if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
logger.warn(
}
if (
connectorId > 0 &&
- (this.getConnectorStatus(connectorId)?.transactionStarted === undefined ||
- this.getConnectorStatus(connectorId)?.transactionStarted === null)
+ Utils.isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionStarted)
) {
- this.initializeConnectorStatus(connectorId);
+ this.initializeConnectorStatus(this.getConnectorStatus(connectorId));
+ }
+ }
+ }
+
+ private buildConnectorsMap(
+ connectors: Record<string, ConnectorStatus>
+ ): Map<number, ConnectorStatus> {
+ const connectorsMap = new Map<number, ConnectorStatus>();
+ for (const connector in connectors) {
+ const connectorStatus = connectors[connector];
+ const connectorId = Utils.convertToInt(connector);
+ this.checkStationInfoConnectorStatus(connectorId, connectorStatus);
+ connectorsMap.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
+ connectorsMap.get(connectorId).availability = AvailabilityType.Operative;
+ if (Utils.isUndefined(connectorsMap.get(connectorId)?.chargingProfiles)) {
+ connectorsMap.get(connectorId).chargingProfiles = [];
+ }
+ }
+ return connectorsMap;
+ }
+
+ private initializeConnectorsMapStatus(connectors: Map<number, ConnectorStatus>): void {
+ for (const connectorId of connectors.keys()) {
+ if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
+ logger.warn(
+ `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
+ connectors.get(connectorId)?.transactionId
+ }`
+ );
+ }
+ if (
+ connectorId > 0 &&
+ Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
+ ) {
+ this.initializeConnectorStatus(connectors.get(connectorId));
+ }
+ }
+ }
+
+ private initializeEvses(stationInfo: ChargingStationInfo): void {
+ if (!stationInfo?.Evses && this.evses.size === 0) {
+ const logMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`;
+ logger.warn(`${this.logPrefix()} ${logMsg}`);
+ return;
+ }
+ if (!stationInfo?.Evses[0]) {
+ logger.warn(
+ `${this.logPrefix()} Charging station information from template ${
+ this.templateFile
+ } with no evse id 0 configuration`
+ );
+ }
+ if (stationInfo?.Evses) {
+ const evsesConfigHash = crypto
+ .createHash(Constants.DEFAULT_HASH_ALGORITHM)
+ .update(`${JSON.stringify(stationInfo?.Evses)}`)
+ .digest('hex');
+ const evsesConfigChanged =
+ this.evses?.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash;
+ if (this.evses?.size === 0 || evsesConfigChanged) {
+ evsesConfigChanged && this.evses.clear();
+ this.evsesConfigurationHash = evsesConfigHash;
+ for (const evse in stationInfo?.Evses) {
+ this.evses.set(Utils.convertToInt(evse), {
+ connectors: this.buildConnectorsMap(stationInfo?.Evses[evse]?.Connectors),
+ availability: AvailabilityType.Operative,
+ });
+ this.initializeConnectorsMapStatus(this.evses.get(Utils.convertToInt(evse))?.connectors);
+ }
}
+ } else {
+ logger.warn(
+ `${this.logPrefix()} Charging station information from template ${
+ this.templateFile
+ } with no evses configuration defined, using already defined evses`
+ );
}
}
// Set default status
connectorStatus = ConnectorStatusEnum.Available;
}
- await this.ocppRequestService.requestHandler<
- StatusNotificationRequest,
- StatusNotificationResponse
- >(
- this,
- RequestCommand.STATUS_NOTIFICATION,
- OCPPServiceUtils.buildStatusNotificationRequest(this, connectorId, connectorStatus)
- );
- this.getConnectorStatus(connectorId).status = connectorStatus;
+ await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorStatus);
}
if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
await this.ocppRequestService.requestHandler<
return this.getTemplateFromFile()?.AutomaticTransactionGenerator;
}
- private initializeConnectorStatus(connectorId: number): void {
- this.getConnectorStatus(connectorId).idTagLocalAuthorized = false;
- this.getConnectorStatus(connectorId).idTagAuthorized = false;
- this.getConnectorStatus(connectorId).transactionRemoteStarted = false;
- this.getConnectorStatus(connectorId).transactionStarted = false;
- this.getConnectorStatus(connectorId).energyActiveImportRegisterValue = 0;
- this.getConnectorStatus(connectorId).transactionEnergyActiveImportRegisterValue = 0;
+ private initializeConnectorStatus(connectorStatus: ConnectorStatus): void {
+ connectorStatus.idTagLocalAuthorized = false;
+ connectorStatus.idTagAuthorized = false;
+ connectorStatus.transactionRemoteStarted = false;
+ connectorStatus.transactionStarted = false;
+ connectorStatus.energyActiveImportRegisterValue = 0;
+ connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
}
}