import { ConnectorStatus } from '../types/ConnectorStatus';
import Constants from '../utils/Constants';
import { ErrorType } from '../types/ocpp/ErrorType';
+import { FileType } from '../types/FileType';
import FileUtils from '../utils/FileUtils';
import { JsonType } from '../types/JsonType';
import { MessageType } from '../types/ocpp/MessageType';
}
this.openWSConnection();
// Monitor authorization file
- this.startAuthorizationFileMonitoring();
- // Monitor station template file
- this.startStationTemplateFileMonitoring();
+ FileUtils.watchJsonFile<string[]>(
+ this.logPrefix(),
+ FileType.Authorization,
+ this.getAuthorizationFile(),
+ this.authorizedTags
+ );
+ // Monitor charging station template file
+ FileUtils.watchJsonFile(
+ this.logPrefix(),
+ FileType.ChargingStationTemplate,
+ this.stationTemplateFile,
+ null,
+ (event, filename): void => {
+ if (filename && event === 'change') {
+ try {
+ logger.debug(
+ `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
+ this.stationTemplateFile
+ } file have changed, reload`
+ );
+ // Initialize
+ this.initialize();
+ // Restart the ATG
+ if (
+ !this.stationInfo.AutomaticTransactionGenerator.enable &&
+ this.automaticTransactionGenerator
+ ) {
+ this.automaticTransactionGenerator.stop();
+ }
+ this.startAutomaticTransactionGenerator();
+ if (this.getEnableStatistics()) {
+ this.performanceStatistics.restart();
+ } else {
+ this.performanceStatistics.stop();
+ }
+ // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
+ } catch (error) {
+ logger.error(
+ `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error: %j`,
+ error
+ );
+ }
+ }
+ }
+ );
+ FileUtils.watchJsonFile<ChargingStationConfiguration>(
+ this.logPrefix(),
+ FileType.ChargingStationConfiguration,
+ this.configurationFile,
+ this.configuration
+ );
// Handle WebSocket message
this.wsConnection.on(
'message',
readonly: false,
visible: true,
reboot: false,
- }
+ },
+ params: { overwrite?: boolean; save?: boolean } = { overwrite: false, save: false }
): void {
- const keyFound = this.getConfigurationKey(key);
+ if (!options || Utils.isEmptyObject(options)) {
+ options = {
+ readonly: false,
+ visible: true,
+ reboot: false,
+ };
+ }
const readonly = options.readonly;
const visible = options.visible;
const reboot = options.reboot;
+ let keyFound = this.getConfigurationKey(key);
+ if (keyFound && params?.overwrite) {
+ this.configuration.configurationKey.splice(
+ this.configuration.configurationKey.indexOf(keyFound),
+ 1
+ );
+ keyFound = undefined;
+ }
if (!keyFound) {
this.configuration.configurationKey.push({
key,
visible,
reboot,
});
+ params?.save && this.saveConfiguration();
} else {
logger.error(
`${this.logPrefix()} Trying to add an already existing configuration key: %j`,
}
}
- public setConfigurationKeyValue(key: string | StandardParametersKey, value: string): void {
- const keyFound = this.getConfigurationKey(key);
+ public setConfigurationKeyValue(
+ key: string | StandardParametersKey,
+ value: string,
+ caseInsensitive = false
+ ): void {
+ const keyFound = this.getConfigurationKey(key, caseInsensitive);
if (keyFound) {
const keyIndex = this.configuration.configurationKey.indexOf(keyFound);
this.configuration.configurationKey[keyIndex].value = value;
let stationTemplateFromFile: ChargingStationTemplate;
try {
// Load template file
- const fileDescriptor = fs.openSync(this.stationTemplateFile, 'r');
stationTemplateFromFile = JSON.parse(
- fs.readFileSync(fileDescriptor, 'utf8')
+ fs.readFileSync(this.stationTemplateFile, 'utf8')
) as ChargingStationTemplate;
- fs.closeSync(fileDescriptor);
} catch (error) {
FileUtils.handleFileException(
this.logPrefix(),
- 'Template',
+ FileType.ChargingStationTemplate,
this.stationTemplateFile,
error as NodeJS.ErrnoException
);
)
) {
this.addConfigurationKey(
- VendorDefaultParametersKey.ConnectionUrl,
+ this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl,
this.getConfiguredSupervisionUrl().href,
{ reboot: true }
);
this.addConfigurationKey(
StandardParametersKey.NumberOfConnectors,
this.getNumberOfConnectors().toString(),
- { readonly: true }
+ { readonly: true },
+ { overwrite: true }
);
if (!this.getConfigurationKey(StandardParametersKey.MeterValuesSampledData)) {
this.addConfigurationKey(
let configuration: ChargingStationConfiguration = null;
if (this.configurationFile && fs.existsSync(this.configurationFile)) {
try {
- const fileDescriptor = fs.openSync(this.configurationFile, 'r');
configuration = JSON.parse(
- fs.readFileSync(fileDescriptor, 'utf8')
+ fs.readFileSync(this.configurationFile, 'utf8')
) as ChargingStationConfiguration;
- fs.closeSync(fileDescriptor);
} catch (error) {
FileUtils.handleFileException(
this.logPrefix(),
- 'Configuration',
+ FileType.ChargingStationConfiguration,
this.configurationFile,
error as NodeJS.ErrnoException
);
} catch (error) {
FileUtils.handleFileException(
this.logPrefix(),
- 'Configuration',
+ FileType.ChargingStationConfiguration,
this.configurationFile,
error as NodeJS.ErrnoException
);
if (authorizationFile) {
try {
// Load authorization file
- const fileDescriptor = fs.openSync(authorizationFile, 'r');
- authorizedTags = JSON.parse(fs.readFileSync(fileDescriptor, 'utf8')) as string[];
- fs.closeSync(fileDescriptor);
+ authorizedTags = JSON.parse(fs.readFileSync(authorizationFile, 'utf8')) as string[];
} catch (error) {
FileUtils.handleFileException(
this.logPrefix(),
- 'Authorization',
+ FileType.Authorization,
authorizationFile,
error as NodeJS.ErrnoException
);
}
}
- private startAuthorizationFileMonitoring(): void {
- const authorizationFile = this.getAuthorizationFile();
- if (authorizationFile) {
- try {
- fs.watch(authorizationFile, (event, filename) => {
- if (filename && event === 'change') {
- try {
- logger.debug(
- this.logPrefix() +
- ' Authorization file ' +
- authorizationFile +
- ' have changed, reload'
- );
- // Initialize authorizedTags
- this.authorizedTags = this.getAuthorizedTags();
- } catch (error) {
- logger.error(this.logPrefix() + ' Authorization file monitoring error: %j', error);
- }
- }
- });
- } catch (error) {
- FileUtils.handleFileException(
- this.logPrefix(),
- 'Authorization',
- authorizationFile,
- error as NodeJS.ErrnoException
- );
- }
- } else {
- logger.info(
- this.logPrefix() +
- ' No authorization file given in template file ' +
- this.stationTemplateFile +
- '. Not monitoring changes'
- );
- }
- }
-
- private startStationTemplateFileMonitoring(): void {
- try {
- fs.watch(this.stationTemplateFile, (event, filename): void => {
- if (filename && event === 'change') {
- try {
- logger.debug(
- this.logPrefix() +
- ' Template file ' +
- this.stationTemplateFile +
- ' have changed, reload'
- );
- // Initialize
- this.initialize();
- // Restart the ATG
- if (
- !this.stationInfo.AutomaticTransactionGenerator.enable &&
- this.automaticTransactionGenerator
- ) {
- this.automaticTransactionGenerator.stop();
- }
- this.startAutomaticTransactionGenerator();
- if (this.getEnableStatistics()) {
- this.performanceStatistics.restart();
- } else {
- this.performanceStatistics.stop();
- }
- // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
- } catch (error) {
- logger.error(
- this.logPrefix() + ' Charging station template file monitoring error: %j',
- error
- );
- }
- }
- });
- } catch (error) {
- FileUtils.handleFileException(
- this.logPrefix(),
- 'Template',
- this.stationTemplateFile,
- error as NodeJS.ErrnoException
- );
- }
- }
-
private getReconnectExponentialDelay(): boolean | undefined {
return !Utils.isUndefined(this.stationInfo.reconnectExponentialDelay)
? this.stationInfo.reconnectExponentialDelay
} else if (keyToChange && keyToChange.readonly) {
return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
} else if (keyToChange && !keyToChange.readonly) {
- const keyIndex = this.chargingStation.configuration.configurationKey.indexOf(keyToChange);
let valueChanged = false;
- if (
- this.chargingStation.configuration.configurationKey[keyIndex].value !== commandPayload.value
- ) {
- this.chargingStation.configuration.configurationKey[keyIndex].value = commandPayload.value;
+ if (keyToChange.value !== commandPayload.value) {
+ this.chargingStation.setConfigurationKeyValue(
+ commandPayload.key,
+ commandPayload.value,
+ true
+ );
valueChanged = true;
}
let triggerHeartbeatRestart = false;
if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
this.chargingStation.addConfigurationKey(
OCPP16StandardParametersKey.HeartBeatInterval,
- payload.interval.toString()
+ payload.interval.toString(),
+ {},
+ { overwrite: true, save: true }
);
this.chargingStation.addConfigurationKey(
OCPP16StandardParametersKey.HeartbeatInterval,
payload.interval.toString(),
- { visible: false }
+ { visible: false },
+ { overwrite: true, save: true }
);
this.chargingStation.heartbeatSetInterval
? this.chargingStation.restartHeartbeat()
// Copyright Jerome Benoit. 2021. All Rights Reserved.
-import Constants from '../../utils/Constants';
+import { FileType } from '../../types/FileType';
import FileUtils from '../../utils/FileUtils';
import Statistics from '../../types/Statistics';
import { Storage } from './Storage';
} catch (error) {
FileUtils.handleFileException(
this.logPrefix,
- Constants.PERFORMANCE_RECORDS_FILETYPE,
+ FileType.PerformanceRecords,
this.dbName,
error as NodeJS.ErrnoException
);
} catch (error) {
FileUtils.handleFileException(
this.logPrefix,
- Constants.PERFORMANCE_RECORDS_FILETYPE,
+ FileType.PerformanceRecords,
this.dbName,
error as NodeJS.ErrnoException
);
} catch (error) {
FileUtils.handleFileException(
this.logPrefix,
- Constants.PERFORMANCE_RECORDS_FILETYPE,
+ FileType.PerformanceRecords,
this.dbName,
error as NodeJS.ErrnoException
);
+import { JsonType } from './JsonType';
import { OCPPConfigurationKey } from './ocpp/Configuration';
export interface ConfigurationKey extends OCPPConfigurationKey {
reboot?: boolean;
}
-export default interface ChargingStationConfiguration {
+export default interface ChargingStationConfiguration extends JsonType {
configurationKey: ConfigurationKey[];
}
--- /dev/null
+export enum FileType {
+ Authorization = 'authorization',
+ Configuration = 'configuration',
+ ChargingStationConfiguration = 'charging station configuration',
+ ChargingStationTemplate = 'charging station template',
+ PerformanceRecords = 'performance records',
+}
type JsonArray = Array<JsonValue>;
-type JsonValue = string | number | boolean | Date | JsonType | JsonArray;
+export type JsonValue = string | number | boolean | Date | JsonType | JsonArray;
export interface JsonType {
[key: string]: JsonValue;
import Constants from './Constants';
import { EmptyObject } from '../types/EmptyObject';
+import { FileType } from '../types/FileType';
import { HandleErrorParams } from '../types/Error';
import { ServerOptions } from 'ws';
import { StorageType } from '../types/Storage';
import path from 'path';
export default class Configuration {
- private static configurationFilePath = path.join(
+ private static configurationFile = path.join(
path.resolve(__dirname, '../'),
'assets',
'config.json'
if (!Configuration.configuration) {
try {
Configuration.configuration = JSON.parse(
- fs.readFileSync(Configuration.configurationFilePath, 'utf8')
+ fs.readFileSync(Configuration.configurationFile, 'utf8')
) as ConfigurationData;
} catch (error) {
Configuration.handleFileException(
Configuration.logPrefix(),
- 'Configuration',
- Configuration.configurationFilePath,
+ FileType.Configuration,
+ Configuration.configurationFile,
error as NodeJS.ErrnoException
);
}
private static getConfigurationFileWatcher(): fs.FSWatcher {
try {
- return fs.watch(Configuration.configurationFilePath, (event, filename): void => {
+ return fs.watch(Configuration.configurationFile, (event, filename): void => {
if (filename && event === 'change') {
// Nullify to force configuration file reading
Configuration.configuration = null;
} catch (error) {
Configuration.handleFileException(
Configuration.logPrefix(),
- 'Configuration',
- Configuration.configurationFilePath,
- error as Error
+ FileType.Configuration,
+ Configuration.configurationFile,
+ error as NodeJS.ErrnoException
);
}
}
private static handleFileException(
logPrefix: string,
- fileType: string,
+ fileType: FileType,
filePath: string,
error: NodeJS.ErrnoException,
params: HandleErrorParams<EmptyObject> = { throwError: true }
static readonly DEFAULT_FLUCTUATION_PERCENT = 5;
- static readonly PERFORMANCE_RECORDS_FILETYPE = 'Performance records';
static readonly DEFAULT_PERFORMANCE_RECORDS_FILENAME = 'performanceRecords.json';
static readonly DEFAULT_PERFORMANCE_RECORDS_DB_NAME = 'charging-stations-simulator';
static readonly PERFORMANCE_RECORDS_TABLE = 'performance_records';
+import { JsonType, JsonValue } from '../types/JsonType';
+
import { EmptyObject } from '../types/EmptyObject';
+import { FileType } from '../types/FileType';
import { HandleErrorParams } from '../types/Error';
import Utils from './Utils';
import chalk from 'chalk';
+import fs from 'fs';
import logger from './Logger';
export default class FileUtils {
+ static watchJsonFile<T extends JsonType | JsonValue>(
+ logPrefix: string,
+ fileType: FileType,
+ file: string,
+ attribute?: T,
+ listener: fs.WatchListener<string> = (event, filename) => {
+ if (filename && event === 'change') {
+ try {
+ logger.debug(logPrefix + ' ' + fileType + ' file ' + file + ' have changed, reload');
+ attribute = JSON.parse(fs.readFileSync(file, 'utf8')) as T;
+ } catch (error) {
+ FileUtils.handleFileException(logPrefix, fileType, file, error as NodeJS.ErrnoException, {
+ throwError: false,
+ });
+ }
+ }
+ }
+ ) {
+ if (file) {
+ try {
+ fs.watch(file, listener);
+ } catch (error) {
+ FileUtils.handleFileException(logPrefix, fileType, file, error as NodeJS.ErrnoException, {
+ throwError: false,
+ });
+ }
+ } else {
+ logger.info(`${logPrefix} No ${fileType} file to watch given. Not monitoring its changes`);
+ }
+ }
+
static handleFileException(
logPrefix: string,
- fileType: string,
- filePath: string,
+ fileType: FileType,
+ file: string,
error: NodeJS.ErrnoException,
params: HandleErrorParams<EmptyObject> = { throwError: true, consoleOut: false }
): void {
if (error.code === 'ENOENT') {
if (params?.consoleOut) {
console.warn(
- chalk.green(prefix) + chalk.yellow(fileType + ' file ' + filePath + ' not found: '),
+ chalk.green(prefix) + chalk.yellow(fileType + ' file ' + file + ' not found: '),
error
);
} else {
- logger.warn(prefix + fileType + ' file ' + filePath + ' not found: %j', error);
+ logger.warn(prefix + fileType + ' file ' + file + ' not found: %j', error);
}
} else if (error.code === 'EEXIST') {
if (params?.consoleOut) {
console.warn(
- chalk.green(prefix) + chalk.yellow(fileType + ' file ' + filePath + ' already exists: '),
+ chalk.green(prefix) + chalk.yellow(fileType + ' file ' + file + ' already exists: '),
error
);
} else {
- logger.warn(prefix + fileType + ' file ' + filePath + ' already exists: %j', error);
+ logger.warn(prefix + fileType + ' file ' + file + ' already exists: %j', error);
}
} else if (error.code === 'EACCES') {
if (params?.consoleOut) {
console.warn(
- chalk.green(prefix) + chalk.yellow(fileType + ' file ' + filePath + ' access denied: '),
+ chalk.green(prefix) + chalk.yellow(fileType + ' file ' + file + ' access denied: '),
error
);
} else {
- logger.warn(prefix + fileType + ' file ' + filePath + ' access denied: %j', error);
+ logger.warn(prefix + fileType + ' file ' + file + ' access denied: %j', error);
}
} else {
if (params?.consoleOut) {
console.warn(
- chalk.green(prefix) + chalk.yellow(fileType + ' file ' + filePath + ' error: '),
+ chalk.green(prefix) + chalk.yellow(fileType + ' file ' + file + ' error: '),
error
);
} else {
- logger.warn(prefix + fileType + ' file ' + filePath + ' error: %j', error);
+ logger.warn(prefix + fileType + ' file ' + file + ' error: %j', error);
}
if (params?.throwError) {
throw error;