UNPKG

@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
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