@citrineos/configuration
Version:
The configuration module for OCPP v2.0.1. This module is not intended to be used directly, but rather as a dependency for other modules.
756 lines • 43.5 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { AbstractModule, AsHandler, BOOT_STATUS, ChargingStationSequenceTypeEnum, ErrorCode, EventGroup, MessageOrigin, MessageState, Namespace, OCPP1_6, OCPP1_6_CallAction, OCPP2_0_1, OCPP2_0_1_CallAction, OcppError, OCPPValidator, OCPPVersion, } from '@citrineos/base';
import { Boot, ChangeConfiguration, ChargingStation, ChargingStationNetworkProfile, Component, sequelize, SequelizeChangeConfigurationRepository, SequelizeChargingStationSequenceRepository, SequelizeOCPPMessageRepository, ServerNetworkProfile, SetNetworkProfile, } from '@citrineos/data';
import { IdGenerator, validateMessageContentType } from '@citrineos/util';
import { Logger } from 'tslog';
import { v4 as uuidv4 } from 'uuid';
import { BootNotificationService } from './BootNotificationService.js';
import { DeviceModelService } from './DeviceModelService.js';
/**
* Component that handles Configuration related messages.
*/
export class ConfigurationModule extends AbstractModule {
_deviceModelService;
_requests = [];
_responses = [];
_bootService;
_idGenerator;
/**
* This is the constructor function that initializes the {@link ConfigurationModule}.
*
* @param {BootstrapConfig & SystemConfig} config - The `config` contains configuration settings for the module.
*
* @param {ICache} [cache] - The cache instance which is shared among the modules & Central System to pass information such as blacklisted actions or boot status.
*
* @param {IMessageSender} [sender] - The `sender` parameter is an optional parameter that represents an instance of the {@link IMessageSender} interface.
* It is used to send messages from the central system to external systems or devices. If no `sender` is provided, a default {@link RabbitMqSender} instance is created and used.
*
* @param {IMessageHandler} [handler] - The `handler` parameter is an optional parameter that represents an instance of the {@link IMessageHandler} interface.
* It is used to handle incoming messages and dispatch them to the appropriate methods or functions. If no `handler` is provided, a default {@link RabbitMqReceiver} instance is created and used.
*
* @param {Logger<ILogObj>} [logger] - The `logger` parameter is an optional parameter that represents an instance of {@link Logger<ILogObj>}.
* It is used to propagate system-wide logger settings and will serve as the parent logger for any subcomponent logging. If no `logger` is provided, a default {@link Logger<ILogObj>} instance is created and used.
*
* @param {IBootRepository} [bootRepository] - An optional parameter of type {@link IBootRepository} which represents a repository for accessing and manipulating authorization data.
* If no `bootRepository` is provided, a default {@link SequelizeBootRepository} instance is created and used.
*
* @param {IDeviceModelRepository} [deviceModelRepository] - An optional parameter of type {@link IDeviceModelRepository} which represents a repository for accessing and manipulating variable data.
* If no `deviceModelRepository` is provided, a default {@link sequelize:deviceModelRepository} instance is created and used.
*
* @param {IMessageInfoRepository} [messageInfoRepository] - An optional parameter of type {@link messageInfoRepository} which
* represents a repository for accessing and manipulating message info data. If no `messageInfoRepository` is provided, a default
* {@link SequelizeMessageInfoRepository} instance is created and used.
*
* @param {ILocationRepository} [locationRepository] - An optional parameter of type {@link locationRepository} which
* represents a repository for accessing and manipulating location data. If no `locationRepository` is provided, a default
* {@link SequelizeLocationRepository} instance is created and used.
*
* @param {IChangeConfigurationRepository} [changeConfigurationRepository] - An optional parameter of type {@link IChangeConfigurationRepository} which
* represents a repository for accessing and manipulating change configuration data. If no `changeConfigurationRepository` is provided, a default
* {@link SequelizeChangeConfigurationRepository} instance is created and used.
*
* @param {IOCPPMessageRepository} [ocppMessageRepository] - An optional parameter of type {@link IOCPPMessageRepository} which
* represents a repository for accessing and manipulating call message data. If no `ocppMessageRepository` is provided, a default
* {@link SequelizeOCPPMessageRepository} instance is created and used.
*
* @param {IdGenerator} [idGenerator] - An optional parameter of type {@link IdGenerator} which
* represents a generator for ids.
*
*If no `deviceModelRepository` is provided, a default {@link sequelize:messageInfoRepository} instance is created and used.
*/
constructor(config, cache, sender, handler, logger, ocppValidator, bootRepository, deviceModelRepository, messageInfoRepository, locationRepository, changeConfigurationRepository, ocppMessageRepository, idGenerator, tenantRepository) {
super(config, cache, handler, sender, EventGroup.Configuration, logger, ocppValidator);
this._requests = config.modules.configuration.requests;
this._responses = config.modules.configuration.responses;
this._bootRepository =
bootRepository || new sequelize.SequelizeBootRepository(config, this._logger);
this._deviceModelRepository =
deviceModelRepository || new sequelize.SequelizeDeviceModelRepository(config, this._logger);
this._messageInfoRepository =
messageInfoRepository || new sequelize.SequelizeMessageInfoRepository(config, this._logger);
this._locationRepository =
locationRepository || new sequelize.SequelizeLocationRepository(config, this._logger);
this._changeConfigurationRepository =
changeConfigurationRepository ||
new SequelizeChangeConfigurationRepository(config, this._logger);
this._ocppMessageRepository =
ocppMessageRepository || new SequelizeOCPPMessageRepository(config, this._logger);
this._tenantRepository =
tenantRepository || new sequelize.SequelizeTenantRepository(config, this._logger);
this._deviceModelService = new DeviceModelService(this._deviceModelRepository);
this._bootService = new BootNotificationService(this._bootRepository, this._cache, this._config.modules.configuration, this._logger);
this._idGenerator =
idGenerator ||
new IdGenerator(new SequelizeChargingStationSequenceRepository(config, this._logger));
}
_tenantRepository;
get tenantRepository() {
return this._tenantRepository;
}
_bootRepository;
get bootRepository() {
return this._bootRepository;
}
_deviceModelRepository;
get deviceModelRepository() {
return this._deviceModelRepository;
}
_messageInfoRepository;
get messageInfoRepository() {
return this._messageInfoRepository;
}
_locationRepository;
get locationRepository() {
return this._locationRepository;
}
_changeConfigurationRepository;
get changeConfigurationRepository() {
return this._changeConfigurationRepository;
}
_ocppMessageRepository;
get ocppMessageRepository() {
return this._ocppMessageRepository;
}
/**
* Handle OCPP 2.0.1 requests
*/
async _handleBootNotification(message, props) {
this._logger.debug('BootNotification received:', message, props);
const stationId = message.context.stationId;
const tenantId = message.context.tenantId;
const timestamp = message.context.timestamp;
const chargingStation = message.payload.chargingStation;
// Quick guard: validate tenant exists before proceeding.
try {
const tenantRecord = await this._tenantRepository.readByKey(tenantId, tenantId);
if (!tenantRecord) {
await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.SecurityError, `Unknown tenant ${tenantId}`, {}));
return;
}
}
catch (err) {
this._logger.warn('Tenant validation failed', err);
await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.SecurityError, `Tenant validation error for ${tenantId}`, {}));
return;
}
const bootNotificationResponse = await this._bootService.createBootNotificationResponse(tenantId, stationId);
// Check cached boot status for charger. Only Pending and Rejected statuses are cached.
const cachedBootStatus = await this._cache.get(BOOT_STATUS, stationId);
// Blacklist or whitelist charger actions in cache
await this._bootService.cacheChargerActionsPermissions(stationId, cachedBootStatus, bootNotificationResponse.status);
const bootNotificationResponseMessageConfirmation = await this.sendCallResultWithMessage(message, bootNotificationResponse);
// Update charging station first, then device model.
// Order matters: updateDeviceModel creates VariableAttributes with a FK
// reference to the ChargingStation record, so the station must exist first.
this._locationRepository
.createOrUpdateChargingStation(tenantId, ChargingStation.build({
tenantId,
id: stationId,
chargePointVendor: chargingStation.vendorName,
chargePointModel: chargingStation.model,
chargePointSerialNumber: chargingStation.serialNumber,
firmwareVersion: chargingStation.firmwareVersion,
iccid: chargingStation.modem?.iccid,
imsi: chargingStation.modem?.imsi,
}))
.then(() => this._deviceModelService.updateDeviceModel(chargingStation, tenantId, stationId, timestamp))
.catch((error) => {
this._logger.error(`Error updating station ${stationId} or device model with boot info:`, error);
});
if (!bootNotificationResponseMessageConfirmation.success) {
throw new Error('BootNotification failed: ' + bootNotificationResponseMessageConfirmation);
}
if (bootNotificationResponse.status !== OCPP2_0_1.RegistrationStatusEnumType.Accepted &&
(!cachedBootStatus || bootNotificationResponse.status !== cachedBootStatus)) {
// Cache boot status for charger if (not accepted) and ((not already cached) or (different status from cached status)).
await this._cache.set(BOOT_STATUS, bootNotificationResponse.status, stationId);
}
// Update charger-specific boot config with details of most recently sent BootNotificationResponse
const bootConfigDbEntity = await this._bootService.updateBootConfig(bootNotificationResponse, tenantId, stationId);
// If boot notification is not pending, do not start configuration.
// If cached boot status is not null and pending, configuration is already in progress - do not start configuration again.
if (bootNotificationResponse.status !== OCPP2_0_1.RegistrationStatusEnumType.Pending ||
(cachedBootStatus && cachedBootStatus === OCPP2_0_1.RegistrationStatusEnumType.Pending)) {
return;
}
// GetBaseReport
// TODO Consider refactoring GetBaseReport and SetVariables sections as methods to be used by their respective message api endpoints as well
if (bootConfigDbEntity.getBaseReportOnPending ??
this._config.modules.configuration.ocpp2_0_1?.getBaseReportOnPending) {
// Remove Notify Report from blacklist
await this._cache.remove(OCPP2_0_1_CallAction.NotifyReport, stationId);
const getBaseReportRequest = await this._bootService.createGetBaseReportRequest(stationId, this._config.maxCachingSeconds);
const getBaseReportConfirmation = await this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetBaseReport, getBaseReportRequest);
await this._bootService.confirmGetBaseReportSuccess(stationId, getBaseReportRequest.requestId.toString(), getBaseReportConfirmation, this._config.maxCachingSeconds);
// Make sure GetBaseReport doesn't re-trigger on next boot attempt
bootConfigDbEntity.getBaseReportOnPending = false;
await bootConfigDbEntity.save();
}
// SetVariables
let rejectedSetVariable = false;
let rebootSetVariable = false;
if (bootConfigDbEntity.pendingBootSetVariables &&
bootConfigDbEntity.pendingBootSetVariables.length > 0) {
bootConfigDbEntity.variablesRejectedOnLastBoot = [];
let setVariableData = await this._deviceModelRepository.readAllSetVariableByStationId(tenantId, stationId);
// If ItemsPerMessageSetVariables not set, send all variables at once
const itemsPerMessageSetVariables = (await this._deviceModelService.getItemsPerMessageSetVariablesByStationId(tenantId, stationId)) ?? setVariableData.length;
while (setVariableData.length > 0) {
const correlationId = uuidv4();
const cacheCallbackPromise = this._cache.onChange(correlationId, this._config.maxCachingSeconds, stationId); // x2 fudge factor for any network lag
await this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.SetVariables, {
setVariableData: setVariableData.slice(0, itemsPerMessageSetVariables),
}, undefined, correlationId);
setVariableData = setVariableData.slice(itemsPerMessageSetVariables);
const setVariablesResponseJsonString = await cacheCallbackPromise;
if (setVariablesResponseJsonString) {
if (rejectedSetVariable && rebootSetVariable) {
continue;
}
const setVariablesResponse = JSON.parse(setVariablesResponseJsonString);
setVariablesResponse.setVariableResult.forEach((result) => {
if (result.attributeStatus === OCPP2_0_1.SetVariableStatusEnumType.Rejected) {
rejectedSetVariable = true;
}
else if (result.attributeStatus === OCPP2_0_1.SetVariableStatusEnumType.RebootRequired) {
rebootSetVariable = true;
}
});
}
else {
throw new Error('SetVariables response not found');
}
}
const doNotBootWithRejectedVariables = !(bootConfigDbEntity.bootWithRejectedVariables ??
this._config.modules.configuration.ocpp2_0_1?.bootWithRejectedVariables);
if (rejectedSetVariable && doNotBootWithRejectedVariables) {
bootConfigDbEntity.status = OCPP2_0_1.RegistrationStatusEnumType.Rejected;
await bootConfigDbEntity.save();
// No more to do.
return;
}
}
if (this._config.modules.configuration.ocpp2_0_1?.autoAccept) {
// Update boot config with status accepted
// TODO: Determine how/if StatusInfo should be generated
bootConfigDbEntity.status = OCPP2_0_1.RegistrationStatusEnumType.Accepted;
await bootConfigDbEntity.save();
}
if (rebootSetVariable) {
// Charger SHALL not be in a transaction as it has not yet successfully booted, therefore it is appropriate to send an Immediate Reset
await this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.Reset, {
type: OCPP2_0_1.ResetEnumType.Immediate,
});
}
else {
// We could trigger the new boot immediately rather than wait for the retry, as nothing more now needs to be done.
// However, B02.FR.02 - Spec allows for TriggerMessageRequest - OCTT fails over trigger
// Commenting out until OCTT behavior changes.
// this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.TriggerMessage,
// { requestedMessage: MessageTriggerEnumType.BootNotification } as TriggerMessageRequest);
}
}
async _handleHeartbeat(message, props) {
this._logger.debug('Heartbeat received:', message, props);
// Create response
const response = {
currentTime: new Date().toISOString(),
};
const messageConfirmation = await this.sendCallResultWithMessage(message, response);
this._logger.debug('Heartbeat response sent: ', messageConfirmation);
}
async _handleNotifyDisplayMessages(message, props) {
// Validate requestId was provided in a previous GetDisplayMessagesRequest
const requestId = message.payload.requestId;
const previousRequest = await this._ocppMessageRepository.readAllByQuery(message.context.tenantId, {
where: {
tenantId: message.context.tenantId,
stationId: message.context.stationId,
action: OCPP2_0_1_CallAction.GetDisplayMessages,
message: {
requestId: requestId,
},
},
limit: 1,
}, Namespace.OCPPMessage);
if (!previousRequest || previousRequest.length === 0) {
await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.PropertyConstraintViolation, 'RequestId was not provided in a GetDisplayMessagesRequest.'));
return;
}
const messageInfoTypes = message.payload.messageInfo;
// Validate message content for each messageInfo item
if (messageInfoTypes && messageInfoTypes.length > 0) {
const validationErrors = [];
for (const messageInfoType of messageInfoTypes) {
const validationResult = validateMessageContentType(messageInfoType.message);
if (!validationResult.isValid) {
validationErrors.push(`Message ID ${messageInfoType.id}: ${validationResult.errorMessage}`);
}
}
if (validationErrors.length > 0) {
const errorMessage = `Message content validation failed: ${validationErrors.join('; ')}`;
const error = new OcppError(message.context.correlationId, ErrorCode.PropertyConstraintViolation, errorMessage);
await this.sendCallErrorWithMessage(message, error);
return;
}
}
this._logger.debug('NotifyDisplayMessages received: ', message, props);
const tenantId = message.context.tenantId;
for (const messageInfoType of messageInfoTypes) {
let componentId;
if (messageInfoType.display) {
const component = await this._deviceModelRepository.findOrCreateEvseAndComponent(tenantId, messageInfoType.display, message.context.stationId);
componentId = component.id;
}
await this._messageInfoRepository.createOrUpdateByMessageInfoTypeAndStationId(tenantId, messageInfoType, message.context.stationId, componentId);
}
// Create response
const response = {};
const messageConfirmation = await this.sendCallResultWithMessage(message, response);
this._logger.debug('NotifyDisplayMessages response sent: ', messageConfirmation);
}
async _handleFirmwareStatusNotification(message, props) {
this._logger.debug('FirmwareStatusNotification received:', message, props);
// TODO: FirmwareStatusNotification is usually triggered. Ideally, it should be sent to the callbackUrl from the message api that sent the trigger message
// Validate requestId requirement
// requestId is mandatory unless message was triggered by TriggerMessageRequest AND no firmware update is ongoing
if (!message.payload.requestId) {
await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.OccurrenceConstraintViolation, 'RequestId is required.'));
return;
}
// Create response
const response = {};
const messageConfirmation = await this.sendCallResultWithMessage(message, response);
this._logger.debug('FirmwareStatusNotification response sent: ', messageConfirmation);
}
/**
* Handle OCPP 2.0.1 responses
*/
_handleChangeAvailability(message, props) {
this._logger.debug('ChangeAvailability response received:', message, props);
}
async _handleSetNetworkProfile(message, props) {
this._logger.debug('SetNetworkProfile response received:', message, props);
if (message.payload.status == OCPP2_0_1.SetNetworkProfileStatusEnumType.Accepted) {
const setNetworkProfile = await SetNetworkProfile.findOne({
where: { correlationId: message.context.correlationId },
});
if (setNetworkProfile) {
const serverNetworkProfile = await ServerNetworkProfile.findByPk(setNetworkProfile.websocketServerConfigId);
if (serverNetworkProfile) {
const chargingStation = await ChargingStation.findByPk(message.context.stationId);
if (chargingStation) {
const [chargingStationNetworkProfile] = await ChargingStationNetworkProfile.findOrBuild({
where: {
stationId: chargingStation.id,
configurationSlot: setNetworkProfile.configurationSlot,
},
});
chargingStationNetworkProfile.websocketServerConfigId =
setNetworkProfile.websocketServerConfigId;
chargingStationNetworkProfile.setNetworkProfileId = setNetworkProfile.id;
await chargingStationNetworkProfile.save();
}
}
}
}
}
_handleGetDisplayMessages(message, props) {
this._logger.debug('GetDisplayMessages response received:', message, props);
}
async _handleSetDisplayMessage(message, props) {
this._logger.debug('SetDisplayMessage response received:', message, props);
const status = message.payload.status;
// when charger station accepts the set message info request
// we trigger a get all display messages request to update stored message info in db
if (status === OCPP2_0_1.DisplayMessageStatusEnumType.Accepted) {
await this._messageInfoRepository.deactivateAllByStationId(message.context.tenantId, message.context.stationId);
await this.sendCall(message.context.stationId, message.context.tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetDisplayMessages, {
requestId: await this._idGenerator.generateRequestId(message.context.tenantId, message.context.stationId, ChargingStationSequenceTypeEnum.getDisplayMessages),
});
}
}
_handlePublishFirmware(message, props) {
this._logger.debug('PublishFirmware response received:', message, props);
}
_handleUnpublishFirmware(message, props) {
this._logger.debug('UnpublishFirmware response received:', message, props);
}
_handleUpdateFirmware(message, props) {
this._logger.debug('UpdateFirmware response received:', message, props);
}
_handleReset(message, props) {
this._logger.debug('Reset response received:', message, props);
}
_handleTriggerMessage(message, props) {
this._logger.debug('TriggerMessage response received:', message, props);
}
async _handleClearDisplayMessage(message, props) {
this._logger.debug('ClearDisplayMessage response received:', message, props);
const status = message.payload.status;
// when charger station accepts the clear message info request
// we trigger a get all display messages request to update stored message info in db
if (status === OCPP2_0_1.ClearMessageStatusEnumType.Accepted) {
await this._messageInfoRepository.deactivateAllByStationId(message.context.tenantId, message.context.stationId);
await this.sendCall(message.context.stationId, message.context.tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetDisplayMessages, {
requestId: await this._idGenerator.generateRequestId(message.context.tenantId, message.context.stationId, ChargingStationSequenceTypeEnum.getDisplayMessages),
});
}
}
// Data Transfer can be either a request and a response
async _handleDataTransfer(message, props) {
this._logger.debug('DataTransfer received:', message, props);
if (message.state === MessageState.Request) {
// Create response
const response = {
status: OCPP2_0_1.DataTransferStatusEnumType.Rejected,
statusInfo: { reasonCode: ErrorCode.NotImplemented },
};
const messageConfirmation = await this.sendCallResultWithMessage(message, response);
this._logger.debug('DataTransfer response sent: ', messageConfirmation);
}
else {
this._logger.debug('DataTransfer response received:', message, props);
}
}
/**
* Handle OCPP 1.6 requests
*/
async _handle16Heartbeat(message, props) {
this._logger.debug('Heartbeat received:', message, props);
const response = {
currentTime: new Date().toISOString(),
};
const messageConfirmation = await this.sendCallResultWithMessage(message, response);
this._logger.debug('Heartbeat response sent: ', messageConfirmation);
}
async _handleOcpp16BootNotification(message, props) {
this._logger.debug('OCPP 1.6 BootNotification request received:', message, props);
const stationId = message.context.stationId;
const tenantId = message.context.tenantId;
const request = message.payload;
// 1. Send BootNotification response
// Create BootNotification response
const bootNotificationResponse = await this._bootService.createOcpp16BootNotificationResponse(tenantId, stationId);
// Check cached boot status for charger. Only Pending and Rejected statuses are cached.
const cachedBootStatus = await this._cache.get(BOOT_STATUS, stationId);
// Blacklist or whitelist charger actions
await this._bootService.cacheOcpp16ChargerActionsPermissions(stationId, cachedBootStatus, bootNotificationResponse.status);
// Send BootNotification response
const bootNotificationResponseMessageConfirmation = await this.sendCallResultWithMessage(message, bootNotificationResponse);
// Create or update charging station
this._logger.debug(`Creating or updating charging station: ${stationId}`);
this._locationRepository
.createOrUpdateChargingStation(tenantId, ChargingStation.build({
tenantId,
id: stationId,
chargePointVendor: request.chargePointVendor,
chargePointModel: request.chargePointModel,
chargePointSerialNumber: request.chargePointSerialNumber,
chargeBoxSerialNumber: request.chargeBoxSerialNumber,
firmwareVersion: request.firmwareVersion,
iccid: request.iccid,
imsi: request.imsi,
meterType: request.meterType,
meterSerialNumber: request.meterSerialNumber,
}))
.then()
.catch((error) => {
this._logger.error(`Error updating station ${stationId} with boot info:`, error);
});
// Check if response was successful
if (!bootNotificationResponseMessageConfirmation.success) {
throw new Error('Send BootNotification response failed: ' + bootNotificationResponseMessageConfirmation);
}
// 2. Update boot status in cache and db entity
// Cache boot status for charger if (not accepted) and ((not already cached) or (different status from cached status)).
if (bootNotificationResponse.status !== OCPP1_6.BootNotificationResponseStatus.Accepted &&
(!cachedBootStatus || bootNotificationResponse.status !== cachedBootStatus)) {
await this._cache.set(BOOT_STATUS, bootNotificationResponse.status, stationId);
}
// Update boot with details of most recently sent BootNotificationResponse
const bootEntity = await this._bootService.updateOcpp16BootConfig(bootNotificationResponse, tenantId, stationId);
// 3. Sync configurations
// If boot notification is not pending, do not start configuration.
// If cached boot status is not null and pending, configuration is already in progress - do not start configuration again.
if (bootNotificationResponse.status !== OCPP1_6.BootNotificationResponseStatus.Pending ||
(cachedBootStatus && cachedBootStatus === OCPP1_6.BootNotificationResponseStatus.Pending)) {
return;
}
let changeConfigurationsOnPending = false;
let getConfigurationsOnPending = true;
// Change Configurations on charging station
const configurations = await this._changeConfigurationRepository.readAllByQuery(tenantId, {
where: {
stationId,
},
});
// Remove ChangeConfiguration call action from blacklist
await this._cache.remove(OCPP1_6_CallAction.ChangeConfiguration, stationId);
// Set each configuration on Charging Station
for (const config of configurations) {
const correlationId = uuidv4();
const cacheCallbackPromise = this._cache.onChange(correlationId, this._config.maxCachingSeconds, stationId);
const changeConfigurationResponseMessageConfirmation = await this.sendCall(stationId, tenantId, OCPPVersion.OCPP1_6, OCPP1_6_CallAction.ChangeConfiguration, {
key: config.key,
value: config.value,
}, undefined, correlationId);
if (!changeConfigurationResponseMessageConfirmation.success) {
changeConfigurationsOnPending = true;
}
// wait before sending next call
await cacheCallbackPromise;
}
// Get Configurations from charging station
// Remove GetConfiguration call action from blacklist
await this._cache.remove(OCPP1_6_CallAction.GetConfiguration, stationId);
// Send GetConfiguration request to charger
const getConfigurationResponseMessageConfirmation = await this.sendCall(stationId, tenantId, OCPPVersion.OCPP1_6, OCPP1_6_CallAction.GetConfiguration, {});
if (getConfigurationResponseMessageConfirmation.success) {
getConfigurationsOnPending = false;
}
// Update configuration related fields on boot entity
await this._bootRepository.updateByKey(tenantId, {
changeConfigurationsOnPending,
getConfigurationsOnPending,
}, bootEntity.id);
// 4. Trigger another boot when pending
await this._cache.remove(OCPP1_6_CallAction.TriggerMessage, stationId);
await this.sendCall(stationId, tenantId, OCPPVersion.OCPP1_6, OCPP1_6_CallAction.TriggerMessage, {
requestedMessage: OCPP1_6.TriggerMessageRequestRequestedMessage.BootNotification,
});
}
/**
* Handle OCPP 1.6 response
*/
async _handleOcpp16GetConfiguration(message, props) {
this._logger.debug('OCPP 1.6 GetConfiguration response received:', message, props);
const tenantId = message.context.tenantId;
const stationId = message.context.stationId;
const configurations = message.payload.configurationKey;
if (configurations && configurations.length > 0) {
for (const config of configurations) {
if (config.key) {
await this._changeConfigurationRepository.createOrUpdateChangeConfiguration(tenantId, {
stationId,
key: config.key,
value: config.value,
readonly: config.readonly,
});
}
}
}
}
async _handleOcpp16ChangeConfiguration(message, props) {
this._logger.debug('OCPP 1.6 ChangeConfiguration response received:', message, props);
const tenantId = message.context.tenantId;
const stationId = message.context.stationId;
const correlationId = message.context.correlationId;
const request = await this._ocppMessageRepository.readOnlyOneByQuery(tenantId, {
where: {
stationId,
correlationId,
origin: MessageOrigin.ChargingStationManagementSystem,
},
});
if (!request) {
this._logger.error(`No valid ChangeConfigurationRequest found for correlationId ${correlationId}`);
}
const status = message.payload.status;
const key = request?.message[3].key;
const value = request?.message[3].value;
if (status == OCPP1_6.ChangeConfigurationResponseStatus.Rejected ||
status == OCPP1_6.ChangeConfigurationResponseStatus.NotSupported) {
this._logger.warn(`Attempted ChangeConfiguration ${correlationId} for ${key}:${value} unsuccessful with status ${status}`);
return;
}
else {
const config = await this._changeConfigurationRepository.createOrUpdateChangeConfiguration(tenantId, {
tenantId,
stationId,
key,
value,
});
if (!config) {
this._logger.error(`Failed to create or update configuration ${key}:${value} on ${stationId}`);
}
else {
this._logger.debug(`Updated changeConfiguration ${key}:${value}`);
}
}
}
_handleOcpp16TriggerMessage(message, props) {
this._logger.debug('TriggerMessage response received:', message, props);
if (message.payload.status !== OCPP1_6.TriggerMessageResponseStatus.Accepted) {
this._logger.error('TriggerMessage failed with status:', message);
}
}
_handle16Reset(message, props) {
this._logger.debug('Reset response received:', message, props);
}
_handleOcpp16ChangeAvailability(message, props) {
this._logger.debug('ChangeAvailability response received:', message, props);
}
// Data Transfer can be either a request or a response
async _handleOcpp16DataTransfer(message, props) {
this._logger.debug('DataTransfer received:', message, props);
if (message.state === MessageState.Request) {
// Create response
const response = {
status: OCPP1_6.DataTransferResponseStatus.Rejected,
};
const messageConfirmation = await this.sendCallResultWithMessage(message, response);
this._logger.debug('DataTransfer response sent: ', messageConfirmation);
}
else {
this._logger.debug('DataTransfer response received:', message, props);
}
}
}
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.BootNotification),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleBootNotification", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.Heartbeat),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleHeartbeat", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.NotifyDisplayMessages),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleNotifyDisplayMessages", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.FirmwareStatusNotification),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleFirmwareStatusNotification", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.ChangeAvailability),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleChangeAvailability", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.SetNetworkProfile),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleSetNetworkProfile", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetDisplayMessages),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleGetDisplayMessages", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.SetDisplayMessage),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleSetDisplayMessage", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.PublishFirmware),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handlePublishFirmware", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.UnpublishFirmware),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleUnpublishFirmware", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.UpdateFirmware),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleUpdateFirmware", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.Reset),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleReset", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.TriggerMessage),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleTriggerMessage", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.ClearDisplayMessage),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleClearDisplayMessage", null);
__decorate([
AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.DataTransfer),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleDataTransfer", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.Heartbeat),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handle16Heartbeat", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.BootNotification),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleOcpp16BootNotification", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.GetConfiguration),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleOcpp16GetConfiguration", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.ChangeConfiguration),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleOcpp16ChangeConfiguration", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.TriggerMessage),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleOcpp16TriggerMessage", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.Reset),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handle16Reset", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.ChangeAvailability),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], ConfigurationModule.prototype, "_handleOcpp16ChangeAvailability", null);
__decorate([
AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.DataTransfer),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], ConfigurationModule.prototype, "_handleOcpp16DataTransfer", null);
//# sourceMappingURL=module.js.map