@zetra/citrineos-configuration 1.8.3-fork.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,733 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { AbstractModule, AsHandler, BOOT_STATUS, ChargingStationSequenceTypeEnum, ErrorCode, EventGroup, MessageOrigin, Namespace, OCPP1_6, OCPP1_6_CallAction, OCPP2_0_1, OCPP2_0_1_CallAction, OcppError, OCPPValidator, OCPPVersion, } from '@citrineos/base';
11
+ import { Boot, ChangeConfiguration, ChargingStation, ChargingStationNetworkProfile, Component, sequelize, SequelizeChangeConfigurationRepository, SequelizeChargingStationSequenceRepository, SequelizeOCPPMessageRepository, ServerNetworkProfile, SetNetworkProfile, } from '@citrineos/data';
12
+ import { IdGenerator, RabbitMqReceiver, RabbitMqSender, validateMessageContentType, } from '@citrineos/util';
13
+ import { Logger } from 'tslog';
14
+ import { v4 as uuidv4 } from 'uuid';
15
+ import { BootNotificationService } from './BootNotificationService.js';
16
+ import { DeviceModelService } from './DeviceModelService.js';
17
+ /**
18
+ * Component that handles Configuration related messages.
19
+ */
20
+ export class ConfigurationModule extends AbstractModule {
21
+ _deviceModelService;
22
+ _requests = [];
23
+ _responses = [];
24
+ _bootService;
25
+ _idGenerator;
26
+ /**
27
+ * This is the constructor function that initializes the {@link ConfigurationModule}.
28
+ *
29
+ * @param {BootstrapConfig & SystemConfig} config - The `config` contains configuration settings for the module.
30
+ *
31
+ * @param {ICache} [cache] - The cache instance which is shared among the modules & Central System to pass information such as blacklisted actions or boot status.
32
+ *
33
+ * @param {IMessageSender} [sender] - The `sender` parameter is an optional parameter that represents an instance of the {@link IMessageSender} interface.
34
+ * 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.
35
+ *
36
+ * @param {IMessageHandler} [handler] - The `handler` parameter is an optional parameter that represents an instance of the {@link IMessageHandler} interface.
37
+ * 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.
38
+ *
39
+ * @param {Logger<ILogObj>} [logger] - The `logger` parameter is an optional parameter that represents an instance of {@link Logger<ILogObj>}.
40
+ * 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.
41
+ *
42
+ * @param {IBootRepository} [bootRepository] - An optional parameter of type {@link IBootRepository} which represents a repository for accessing and manipulating authorization data.
43
+ * If no `bootRepository` is provided, a default {@link SequelizeBootRepository} instance is created and used.
44
+ *
45
+ * @param {IDeviceModelRepository} [deviceModelRepository] - An optional parameter of type {@link IDeviceModelRepository} which represents a repository for accessing and manipulating variable data.
46
+ * If no `deviceModelRepository` is provided, a default {@link sequelize:deviceModelRepository} instance is created and used.
47
+ *
48
+ * @param {IMessageInfoRepository} [messageInfoRepository] - An optional parameter of type {@link messageInfoRepository} which
49
+ * represents a repository for accessing and manipulating message info data. If no `messageInfoRepository` is provided, a default
50
+ * {@link SequelizeMessageInfoRepository} instance is created and used.
51
+ *
52
+ * @param {ILocationRepository} [locationRepository] - An optional parameter of type {@link locationRepository} which
53
+ * represents a repository for accessing and manipulating location data. If no `locationRepository` is provided, a default
54
+ * {@link SequelizeLocationRepository} instance is created and used.
55
+ *
56
+ * @param {IChangeConfigurationRepository} [changeConfigurationRepository] - An optional parameter of type {@link IChangeConfigurationRepository} which
57
+ * represents a repository for accessing and manipulating change configuration data. If no `changeConfigurationRepository` is provided, a default
58
+ * {@link SequelizeChangeConfigurationRepository} instance is created and used.
59
+ *
60
+ * @param {IOCPPMessageRepository} [ocppMessageRepository] - An optional parameter of type {@link IOCPPMessageRepository} which
61
+ * represents a repository for accessing and manipulating call message data. If no `ocppMessageRepository` is provided, a default
62
+ * {@link SequelizeOCPPMessageRepository} instance is created and used.
63
+ *
64
+ * @param {IdGenerator} [idGenerator] - An optional parameter of type {@link IdGenerator} which
65
+ * represents a generator for ids.
66
+ *
67
+ *If no `deviceModelRepository` is provided, a default {@link sequelize:messageInfoRepository} instance is created and used.
68
+ */
69
+ constructor(config, cache, sender, handler, logger, ocppValidator, bootRepository, deviceModelRepository, messageInfoRepository, locationRepository, changeConfigurationRepository, ocppMessageRepository, idGenerator, tenantRepository) {
70
+ super(config, cache, handler || new RabbitMqReceiver(config, logger), sender || new RabbitMqSender(config, logger), EventGroup.Configuration, logger, ocppValidator);
71
+ this._requests = config.modules.configuration.requests;
72
+ this._responses = config.modules.configuration.responses;
73
+ this._bootRepository =
74
+ bootRepository || new sequelize.SequelizeBootRepository(config, this._logger);
75
+ this._deviceModelRepository =
76
+ deviceModelRepository || new sequelize.SequelizeDeviceModelRepository(config, this._logger);
77
+ this._messageInfoRepository =
78
+ messageInfoRepository || new sequelize.SequelizeMessageInfoRepository(config, this._logger);
79
+ this._locationRepository =
80
+ locationRepository || new sequelize.SequelizeLocationRepository(config, this._logger);
81
+ this._changeConfigurationRepository =
82
+ changeConfigurationRepository ||
83
+ new SequelizeChangeConfigurationRepository(config, this._logger);
84
+ this._ocppMessageRepository =
85
+ ocppMessageRepository || new SequelizeOCPPMessageRepository(config, this._logger);
86
+ this._tenantRepository =
87
+ tenantRepository || new sequelize.SequelizeTenantRepository(config, this._logger);
88
+ this._deviceModelService = new DeviceModelService(this._deviceModelRepository);
89
+ this._bootService = new BootNotificationService(this._bootRepository, this._cache, this._config.modules.configuration, this._logger);
90
+ this._idGenerator =
91
+ idGenerator ||
92
+ new IdGenerator(new SequelizeChargingStationSequenceRepository(config, this._logger));
93
+ }
94
+ _tenantRepository;
95
+ get tenantRepository() {
96
+ return this._tenantRepository;
97
+ }
98
+ _bootRepository;
99
+ get bootRepository() {
100
+ return this._bootRepository;
101
+ }
102
+ _deviceModelRepository;
103
+ get deviceModelRepository() {
104
+ return this._deviceModelRepository;
105
+ }
106
+ _messageInfoRepository;
107
+ get messageInfoRepository() {
108
+ return this._messageInfoRepository;
109
+ }
110
+ _locationRepository;
111
+ get locationRepository() {
112
+ return this._locationRepository;
113
+ }
114
+ _changeConfigurationRepository;
115
+ get changeConfigurationRepository() {
116
+ return this._changeConfigurationRepository;
117
+ }
118
+ _ocppMessageRepository;
119
+ get ocppMessageRepository() {
120
+ return this._ocppMessageRepository;
121
+ }
122
+ /**
123
+ * Handle OCPP 2.0.1 requests
124
+ */
125
+ async _handleBootNotification(message, props) {
126
+ this._logger.debug('BootNotification received:', message, props);
127
+ const stationId = message.context.stationId;
128
+ const tenantId = message.context.tenantId;
129
+ const timestamp = message.context.timestamp;
130
+ const chargingStation = message.payload.chargingStation;
131
+ // Quick guard: validate tenant exists before proceeding.
132
+ try {
133
+ const tenantRecord = await this._tenantRepository.readByKey(tenantId, tenantId);
134
+ if (!tenantRecord) {
135
+ await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.SecurityError, `Unknown tenant ${tenantId}`, {}));
136
+ return;
137
+ }
138
+ }
139
+ catch (err) {
140
+ this._logger.warn('Tenant validation failed', err);
141
+ await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.SecurityError, `Tenant validation error for ${tenantId}`, {}));
142
+ return;
143
+ }
144
+ const bootNotificationResponse = await this._bootService.createBootNotificationResponse(tenantId, stationId);
145
+ // Check cached boot status for charger. Only Pending and Rejected statuses are cached.
146
+ const cachedBootStatus = await this._cache.get(BOOT_STATUS, stationId);
147
+ // Blacklist or whitelist charger actions in cache
148
+ await this._bootService.cacheChargerActionsPermissions(stationId, cachedBootStatus, bootNotificationResponse.status);
149
+ const bootNotificationResponseMessageConfirmation = await this.sendCallResultWithMessage(message, bootNotificationResponse);
150
+ // Update device model and charging station
151
+ this._deviceModelService
152
+ .updateDeviceModel(chargingStation, tenantId, stationId, timestamp)
153
+ .then()
154
+ .catch((error) => {
155
+ this._logger.error(`Error updating device model for station ${stationId} with boot info:`, error);
156
+ });
157
+ this._locationRepository
158
+ .createOrUpdateChargingStation(tenantId, ChargingStation.build({
159
+ tenantId,
160
+ id: stationId,
161
+ chargePointVendor: chargingStation.vendorName,
162
+ chargePointModel: chargingStation.model,
163
+ chargePointSerialNumber: chargingStation.serialNumber,
164
+ firmwareVersion: chargingStation.firmwareVersion,
165
+ iccid: chargingStation.modem?.iccid,
166
+ imsi: chargingStation.modem?.imsi,
167
+ }))
168
+ .then()
169
+ .catch((error) => {
170
+ this._logger.error(`Error updating station ${stationId} with boot info:`, error);
171
+ });
172
+ if (!bootNotificationResponseMessageConfirmation.success) {
173
+ throw new Error('BootNotification failed: ' + bootNotificationResponseMessageConfirmation);
174
+ }
175
+ if (bootNotificationResponse.status !== OCPP2_0_1.RegistrationStatusEnumType.Accepted &&
176
+ (!cachedBootStatus || bootNotificationResponse.status !== cachedBootStatus)) {
177
+ // Cache boot status for charger if (not accepted) and ((not already cached) or (different status from cached status)).
178
+ await this._cache.set(BOOT_STATUS, bootNotificationResponse.status, stationId);
179
+ }
180
+ // Update charger-specific boot config with details of most recently sent BootNotificationResponse
181
+ const bootConfigDbEntity = await this._bootService.updateBootConfig(bootNotificationResponse, tenantId, stationId);
182
+ // If boot notification is not pending, do not start configuration.
183
+ // If cached boot status is not null and pending, configuration is already in progress - do not start configuration again.
184
+ if (bootNotificationResponse.status !== OCPP2_0_1.RegistrationStatusEnumType.Pending ||
185
+ (cachedBootStatus && cachedBootStatus === OCPP2_0_1.RegistrationStatusEnumType.Pending)) {
186
+ return;
187
+ }
188
+ // GetBaseReport
189
+ // TODO Consider refactoring GetBaseReport and SetVariables sections as methods to be used by their respective message api endpoints as well
190
+ if (bootConfigDbEntity.getBaseReportOnPending ??
191
+ this._config.modules.configuration.ocpp2_0_1?.getBaseReportOnPending) {
192
+ // Remove Notify Report from blacklist
193
+ await this._cache.remove(OCPP2_0_1_CallAction.NotifyReport, stationId);
194
+ const getBaseReportRequest = await this._bootService.createGetBaseReportRequest(stationId, this._config.maxCachingSeconds);
195
+ const getBaseReportConfirmation = await this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetBaseReport, getBaseReportRequest);
196
+ await this._bootService.confirmGetBaseReportSuccess(stationId, getBaseReportRequest.requestId.toString(), getBaseReportConfirmation, this._config.maxCachingSeconds);
197
+ // Make sure GetBaseReport doesn't re-trigger on next boot attempt
198
+ bootConfigDbEntity.getBaseReportOnPending = false;
199
+ await bootConfigDbEntity.save();
200
+ }
201
+ // SetVariables
202
+ let rejectedSetVariable = false;
203
+ let rebootSetVariable = false;
204
+ if (bootConfigDbEntity.pendingBootSetVariables &&
205
+ bootConfigDbEntity.pendingBootSetVariables.length > 0) {
206
+ bootConfigDbEntity.variablesRejectedOnLastBoot = [];
207
+ let setVariableData = await this._deviceModelRepository.readAllSetVariableByStationId(tenantId, stationId);
208
+ // If ItemsPerMessageSetVariables not set, send all variables at once
209
+ const itemsPerMessageSetVariables = (await this._deviceModelService.getItemsPerMessageSetVariablesByStationId(tenantId, stationId)) ?? setVariableData.length;
210
+ while (setVariableData.length > 0) {
211
+ const correlationId = uuidv4();
212
+ const cacheCallbackPromise = this._cache.onChange(correlationId, this._config.maxCachingSeconds, stationId); // x2 fudge factor for any network lag
213
+ await this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.SetVariables, {
214
+ setVariableData: setVariableData.slice(0, itemsPerMessageSetVariables),
215
+ }, undefined, correlationId);
216
+ setVariableData = setVariableData.slice(itemsPerMessageSetVariables);
217
+ const setVariablesResponseJsonString = await cacheCallbackPromise;
218
+ if (setVariablesResponseJsonString) {
219
+ if (rejectedSetVariable && rebootSetVariable) {
220
+ continue;
221
+ }
222
+ const setVariablesResponse = JSON.parse(setVariablesResponseJsonString);
223
+ setVariablesResponse.setVariableResult.forEach((result) => {
224
+ if (result.attributeStatus === OCPP2_0_1.SetVariableStatusEnumType.Rejected) {
225
+ rejectedSetVariable = true;
226
+ }
227
+ else if (result.attributeStatus === OCPP2_0_1.SetVariableStatusEnumType.RebootRequired) {
228
+ rebootSetVariable = true;
229
+ }
230
+ });
231
+ }
232
+ else {
233
+ throw new Error('SetVariables response not found');
234
+ }
235
+ }
236
+ const doNotBootWithRejectedVariables = !(bootConfigDbEntity.bootWithRejectedVariables ??
237
+ this._config.modules.configuration.ocpp2_0_1?.bootWithRejectedVariables);
238
+ if (rejectedSetVariable && doNotBootWithRejectedVariables) {
239
+ bootConfigDbEntity.status = OCPP2_0_1.RegistrationStatusEnumType.Rejected;
240
+ await bootConfigDbEntity.save();
241
+ // No more to do.
242
+ return;
243
+ }
244
+ }
245
+ if (this._config.modules.configuration.ocpp2_0_1?.autoAccept) {
246
+ // Update boot config with status accepted
247
+ // TODO: Determine how/if StatusInfo should be generated
248
+ bootConfigDbEntity.status = OCPP2_0_1.RegistrationStatusEnumType.Accepted;
249
+ await bootConfigDbEntity.save();
250
+ }
251
+ if (rebootSetVariable) {
252
+ // Charger SHALL not be in a transaction as it has not yet successfully booted, therefore it is appropriate to send an Immediate Reset
253
+ await this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.Reset, {
254
+ type: OCPP2_0_1.ResetEnumType.Immediate,
255
+ });
256
+ }
257
+ else {
258
+ // We could trigger the new boot immediately rather than wait for the retry, as nothing more now needs to be done.
259
+ // However, B02.FR.02 - Spec allows for TriggerMessageRequest - OCTT fails over trigger
260
+ // Commenting out until OCTT behavior changes.
261
+ // this.sendCall(stationId, tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.TriggerMessage,
262
+ // { requestedMessage: MessageTriggerEnumType.BootNotification } as TriggerMessageRequest);
263
+ }
264
+ }
265
+ async _handleHeartbeat(message, props) {
266
+ this._logger.debug('Heartbeat received:', message, props);
267
+ // Create response
268
+ const response = {
269
+ currentTime: new Date().toISOString(),
270
+ };
271
+ const messageConfirmation = await this.sendCallResultWithMessage(message, response);
272
+ this._logger.debug('Heartbeat response sent: ', messageConfirmation);
273
+ }
274
+ async _handleNotifyDisplayMessages(message, props) {
275
+ // Validate requestId was provided in a previous GetDisplayMessagesRequest
276
+ const requestId = message.payload.requestId;
277
+ const previousRequest = await this._ocppMessageRepository.readAllByQuery(message.context.tenantId, {
278
+ where: {
279
+ tenantId: message.context.tenantId,
280
+ stationId: message.context.stationId,
281
+ action: OCPP2_0_1_CallAction.GetDisplayMessages,
282
+ message: {
283
+ requestId: requestId,
284
+ },
285
+ },
286
+ limit: 1,
287
+ }, Namespace.OCPPMessage);
288
+ if (!previousRequest || previousRequest.length === 0) {
289
+ await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.PropertyConstraintViolation, 'RequestId was not provided in a GetDisplayMessagesRequest.'));
290
+ return;
291
+ }
292
+ const messageInfoTypes = message.payload.messageInfo;
293
+ // Validate message content for each messageInfo item
294
+ if (messageInfoTypes && messageInfoTypes.length > 0) {
295
+ const validationErrors = [];
296
+ for (const messageInfoType of messageInfoTypes) {
297
+ const validationResult = validateMessageContentType(messageInfoType.message);
298
+ if (!validationResult.isValid) {
299
+ validationErrors.push(`Message ID ${messageInfoType.id}: ${validationResult.errorMessage}`);
300
+ }
301
+ }
302
+ if (validationErrors.length > 0) {
303
+ const errorMessage = `Message content validation failed: ${validationErrors.join('; ')}`;
304
+ const error = new OcppError(message.context.correlationId, ErrorCode.PropertyConstraintViolation, errorMessage);
305
+ await this.sendCallErrorWithMessage(message, error);
306
+ return;
307
+ }
308
+ }
309
+ this._logger.debug('NotifyDisplayMessages received: ', message, props);
310
+ const tenantId = message.context.tenantId;
311
+ for (const messageInfoType of messageInfoTypes) {
312
+ let componentId;
313
+ if (messageInfoType.display) {
314
+ const component = await this._deviceModelRepository.findOrCreateEvseAndComponent(tenantId, messageInfoType.display, message.context.stationId);
315
+ componentId = component.id;
316
+ }
317
+ await this._messageInfoRepository.createOrUpdateByMessageInfoTypeAndStationId(tenantId, messageInfoType, message.context.stationId, componentId);
318
+ }
319
+ // Create response
320
+ const response = {};
321
+ const messageConfirmation = await this.sendCallResultWithMessage(message, response);
322
+ this._logger.debug('NotifyDisplayMessages response sent: ', messageConfirmation);
323
+ }
324
+ async _handleFirmwareStatusNotification(message, props) {
325
+ this._logger.debug('FirmwareStatusNotification received:', message, props);
326
+ // TODO: FirmwareStatusNotification is usually triggered. Ideally, it should be sent to the callbackUrl from the message api that sent the trigger message
327
+ // Validate requestId requirement
328
+ // requestId is mandatory unless message was triggered by TriggerMessageRequest AND no firmware update is ongoing
329
+ if (!message.payload.requestId) {
330
+ await this.sendCallErrorWithMessage(message, new OcppError(message.context.correlationId, ErrorCode.OccurrenceConstraintViolation, 'RequestId is required.'));
331
+ return;
332
+ }
333
+ // Create response
334
+ const response = {};
335
+ const messageConfirmation = await this.sendCallResultWithMessage(message, response);
336
+ this._logger.debug('FirmwareStatusNotification response sent: ', messageConfirmation);
337
+ }
338
+ async _handleDataTransfer(message, props) {
339
+ this._logger.debug('DataTransfer received:', message, props);
340
+ // Create response
341
+ const response = {
342
+ status: OCPP2_0_1.DataTransferStatusEnumType.Rejected,
343
+ statusInfo: { reasonCode: ErrorCode.NotImplemented },
344
+ };
345
+ const messageConfirmation = await this.sendCallResultWithMessage(message, response);
346
+ this._logger.debug('DataTransfer response sent: ', messageConfirmation);
347
+ }
348
+ /**
349
+ * Handle OCPP 2.0.1 responses
350
+ */
351
+ _handleChangeAvailability(message, props) {
352
+ this._logger.debug('ChangeAvailability response received:', message, props);
353
+ }
354
+ async _handleSetNetworkProfile(message, props) {
355
+ this._logger.debug('SetNetworkProfile response received:', message, props);
356
+ if (message.payload.status == OCPP2_0_1.SetNetworkProfileStatusEnumType.Accepted) {
357
+ const setNetworkProfile = await SetNetworkProfile.findOne({
358
+ where: { correlationId: message.context.correlationId },
359
+ });
360
+ if (setNetworkProfile) {
361
+ const serverNetworkProfile = await ServerNetworkProfile.findByPk(setNetworkProfile.websocketServerConfigId);
362
+ if (serverNetworkProfile) {
363
+ const chargingStation = await ChargingStation.findByPk(message.context.stationId);
364
+ if (chargingStation) {
365
+ const [chargingStationNetworkProfile] = await ChargingStationNetworkProfile.findOrBuild({
366
+ where: {
367
+ stationId: chargingStation.id,
368
+ configurationSlot: setNetworkProfile.configurationSlot,
369
+ },
370
+ });
371
+ chargingStationNetworkProfile.websocketServerConfigId =
372
+ setNetworkProfile.websocketServerConfigId;
373
+ chargingStationNetworkProfile.setNetworkProfileId = setNetworkProfile.id;
374
+ await chargingStationNetworkProfile.save();
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+ _handleGetDisplayMessages(message, props) {
381
+ this._logger.debug('GetDisplayMessages response received:', message, props);
382
+ }
383
+ async _handleSetDisplayMessage(message, props) {
384
+ this._logger.debug('SetDisplayMessage response received:', message, props);
385
+ const status = message.payload.status;
386
+ // when charger station accepts the set message info request
387
+ // we trigger a get all display messages request to update stored message info in db
388
+ if (status === OCPP2_0_1.DisplayMessageStatusEnumType.Accepted) {
389
+ await this._messageInfoRepository.deactivateAllByStationId(message.context.tenantId, message.context.stationId);
390
+ await this.sendCall(message.context.stationId, message.context.tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetDisplayMessages, {
391
+ requestId: await this._idGenerator.generateRequestId(message.context.tenantId, message.context.stationId, ChargingStationSequenceTypeEnum.getDisplayMessages),
392
+ });
393
+ }
394
+ }
395
+ _handlePublishFirmware(message, props) {
396
+ this._logger.debug('PublishFirmware response received:', message, props);
397
+ }
398
+ _handleUnpublishFirmware(message, props) {
399
+ this._logger.debug('UnpublishFirmware response received:', message, props);
400
+ }
401
+ _handleUpdateFirmware(message, props) {
402
+ this._logger.debug('UpdateFirmware response received:', message, props);
403
+ }
404
+ _handleReset(message, props) {
405
+ this._logger.debug('Reset response received:', message, props);
406
+ }
407
+ _handleTriggerMessage(message, props) {
408
+ this._logger.debug('TriggerMessage response received:', message, props);
409
+ }
410
+ async _handleClearDisplayMessage(message, props) {
411
+ this._logger.debug('ClearDisplayMessage response received:', message, props);
412
+ const status = message.payload.status;
413
+ // when charger station accepts the clear message info request
414
+ // we trigger a get all display messages request to update stored message info in db
415
+ if (status === OCPP2_0_1.ClearMessageStatusEnumType.Accepted) {
416
+ await this._messageInfoRepository.deactivateAllByStationId(message.context.tenantId, message.context.stationId);
417
+ await this.sendCall(message.context.stationId, message.context.tenantId, OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetDisplayMessages, {
418
+ requestId: await this._idGenerator.generateRequestId(message.context.tenantId, message.context.stationId, ChargingStationSequenceTypeEnum.getDisplayMessages),
419
+ });
420
+ }
421
+ }
422
+ /**
423
+ * Handle OCPP 1.6 requests
424
+ */
425
+ async _handle16Heartbeat(message, props) {
426
+ this._logger.debug('Heartbeat received:', message, props);
427
+ const response = {
428
+ currentTime: new Date().toISOString(),
429
+ };
430
+ const messageConfirmation = await this.sendCallResultWithMessage(message, response);
431
+ this._logger.debug('Heartbeat response sent: ', messageConfirmation);
432
+ }
433
+ async _handleOcpp16BootNotification(message, props) {
434
+ this._logger.debug('OCPP 1.6 BootNotification request received:', message, props);
435
+ const stationId = message.context.stationId;
436
+ const tenantId = message.context.tenantId;
437
+ const request = message.payload;
438
+ // 1. Send BootNotification response
439
+ // Create BootNotification response
440
+ const bootNotificationResponse = await this._bootService.createOcpp16BootNotificationResponse(tenantId, stationId);
441
+ // Check cached boot status for charger. Only Pending and Rejected statuses are cached.
442
+ const cachedBootStatus = await this._cache.get(BOOT_STATUS, stationId);
443
+ // Blacklist or whitelist charger actions
444
+ await this._bootService.cacheOcpp16ChargerActionsPermissions(stationId, cachedBootStatus, bootNotificationResponse.status);
445
+ // Send BootNotification response
446
+ const bootNotificationResponseMessageConfirmation = await this.sendCallResultWithMessage(message, bootNotificationResponse);
447
+ // Create or update charging station
448
+ this._logger.debug(`Creating or updating charging station: ${stationId}`);
449
+ this._locationRepository
450
+ .createOrUpdateChargingStation(tenantId, ChargingStation.build({
451
+ tenantId,
452
+ id: stationId,
453
+ chargePointVendor: request.chargePointVendor,
454
+ chargePointModel: request.chargePointModel,
455
+ chargePointSerialNumber: request.chargePointSerialNumber,
456
+ chargeBoxSerialNumber: request.chargeBoxSerialNumber,
457
+ firmwareVersion: request.firmwareVersion,
458
+ iccid: request.iccid,
459
+ imsi: request.imsi,
460
+ meterType: request.meterType,
461
+ meterSerialNumber: request.meterSerialNumber,
462
+ }))
463
+ .then()
464
+ .catch((error) => {
465
+ this._logger.error(`Error updating station ${stationId} with boot info:`, error);
466
+ });
467
+ // Check if response was successful
468
+ if (!bootNotificationResponseMessageConfirmation.success) {
469
+ throw new Error('Send BootNotification response failed: ' + bootNotificationResponseMessageConfirmation);
470
+ }
471
+ // 2. Update boot status in cache and db entity
472
+ // Cache boot status for charger if (not accepted) and ((not already cached) or (different status from cached status)).
473
+ if (bootNotificationResponse.status !== OCPP1_6.BootNotificationResponseStatus.Accepted &&
474
+ (!cachedBootStatus || bootNotificationResponse.status !== cachedBootStatus)) {
475
+ await this._cache.set(BOOT_STATUS, bootNotificationResponse.status, stationId);
476
+ }
477
+ // Update boot with details of most recently sent BootNotificationResponse
478
+ const bootEntity = await this._bootService.updateOcpp16BootConfig(bootNotificationResponse, tenantId, stationId);
479
+ // 3. Sync configurations
480
+ // If boot notification is not pending, do not start configuration.
481
+ // If cached boot status is not null and pending, configuration is already in progress - do not start configuration again.
482
+ if (bootNotificationResponse.status !== OCPP1_6.BootNotificationResponseStatus.Pending ||
483
+ (cachedBootStatus && cachedBootStatus === OCPP1_6.BootNotificationResponseStatus.Pending)) {
484
+ return;
485
+ }
486
+ let changeConfigurationsOnPending = false;
487
+ let getConfigurationsOnPending = true;
488
+ // Change Configurations on charging station
489
+ const configurations = await this._changeConfigurationRepository.readAllByQuery(tenantId, {
490
+ where: {
491
+ stationId,
492
+ },
493
+ });
494
+ // Remove ChangeConfiguration call action from blacklist
495
+ await this._cache.remove(OCPP1_6_CallAction.ChangeConfiguration, stationId);
496
+ // Set each configuration on Charging Station
497
+ for (const config of configurations) {
498
+ const correlationId = uuidv4();
499
+ const cacheCallbackPromise = this._cache.onChange(correlationId, this._config.maxCachingSeconds, stationId);
500
+ const changeConfigurationResponseMessageConfirmation = await this.sendCall(stationId, tenantId, OCPPVersion.OCPP1_6, OCPP1_6_CallAction.ChangeConfiguration, {
501
+ key: config.key,
502
+ value: config.value,
503
+ }, undefined, correlationId);
504
+ if (!changeConfigurationResponseMessageConfirmation.success) {
505
+ changeConfigurationsOnPending = true;
506
+ }
507
+ // wait before sending next call
508
+ await cacheCallbackPromise;
509
+ }
510
+ // Get Configurations from charging station
511
+ // Remove GetConfiguration call action from blacklist
512
+ await this._cache.remove(OCPP1_6_CallAction.GetConfiguration, stationId);
513
+ // Send GetConfiguration request to charger
514
+ const getConfigurationResponseMessageConfirmation = await this.sendCall(stationId, tenantId, OCPPVersion.OCPP1_6, OCPP1_6_CallAction.GetConfiguration, {});
515
+ if (getConfigurationResponseMessageConfirmation.success) {
516
+ getConfigurationsOnPending = false;
517
+ }
518
+ // Update configuration related fields on boot entity
519
+ await this._bootRepository.updateByKey(tenantId, {
520
+ changeConfigurationsOnPending,
521
+ getConfigurationsOnPending,
522
+ }, bootEntity.id);
523
+ // 4. Trigger another boot when pending
524
+ await this._cache.remove(OCPP1_6_CallAction.TriggerMessage, stationId);
525
+ await this.sendCall(stationId, tenantId, OCPPVersion.OCPP1_6, OCPP1_6_CallAction.TriggerMessage, {
526
+ requestedMessage: OCPP1_6.TriggerMessageRequestRequestedMessage.BootNotification,
527
+ });
528
+ }
529
+ /**
530
+ * Handle OCPP 1.6 response
531
+ */
532
+ async _handleOcpp16GetConfiguration(message, props) {
533
+ this._logger.debug('OCPP 1.6 GetConfiguration response received:', message, props);
534
+ const tenantId = message.context.tenantId;
535
+ const stationId = message.context.stationId;
536
+ const configurations = message.payload.configurationKey;
537
+ if (configurations && configurations.length > 0) {
538
+ for (const config of configurations) {
539
+ if (config.key) {
540
+ await this._changeConfigurationRepository.createOrUpdateChangeConfiguration(tenantId, {
541
+ stationId,
542
+ key: config.key,
543
+ value: config.value,
544
+ readonly: config.readonly,
545
+ });
546
+ }
547
+ }
548
+ }
549
+ }
550
+ async _handleOcpp16ChangeConfiguration(message, props) {
551
+ this._logger.debug('OCPP 1.6 ChangeConfiguration response received:', message, props);
552
+ const tenantId = message.context.tenantId;
553
+ const stationId = message.context.stationId;
554
+ const correlationId = message.context.correlationId;
555
+ const request = await this._ocppMessageRepository.readOnlyOneByQuery(tenantId, {
556
+ where: {
557
+ stationId,
558
+ correlationId,
559
+ origin: MessageOrigin.ChargingStationManagementSystem,
560
+ },
561
+ });
562
+ if (!request) {
563
+ this._logger.error(`No valid ChangeConfigurationRequest found for correlationId ${correlationId}`);
564
+ }
565
+ const status = message.payload.status;
566
+ const key = request?.message[3].key;
567
+ const value = request?.message[3].value;
568
+ if (status == OCPP1_6.ChangeConfigurationResponseStatus.Rejected ||
569
+ status == OCPP1_6.ChangeConfigurationResponseStatus.NotSupported) {
570
+ this._logger.warn(`Attempted ChangeConfiguration ${correlationId} for ${key}:${value} unsuccessful with status ${status}`);
571
+ return;
572
+ }
573
+ else {
574
+ const config = await this._changeConfigurationRepository.createOrUpdateChangeConfiguration(tenantId, {
575
+ tenantId,
576
+ stationId,
577
+ key,
578
+ value,
579
+ });
580
+ if (!config) {
581
+ this._logger.error(`Failed to create or update configuration ${key}:${value} on ${stationId}`);
582
+ }
583
+ else {
584
+ this._logger.debug(`Updated changeConfiguration ${key}:${value}`);
585
+ }
586
+ }
587
+ }
588
+ _handleOcpp16TriggerMessage(message, props) {
589
+ this._logger.debug('TriggerMessage response received:', message, props);
590
+ if (message.payload.status !== OCPP1_6.TriggerMessageResponseStatus.Accepted) {
591
+ this._logger.error('TriggerMessage failed with status:', message);
592
+ }
593
+ }
594
+ _handle16Reset(message, props) {
595
+ this._logger.debug('Reset response received:', message, props);
596
+ }
597
+ _handleOcpp16ChangeAvailability(message, props) {
598
+ this._logger.debug('ChangeAvailability response received:', message, props);
599
+ }
600
+ }
601
+ __decorate([
602
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.BootNotification),
603
+ __metadata("design:type", Function),
604
+ __metadata("design:paramtypes", [Object, Object]),
605
+ __metadata("design:returntype", Promise)
606
+ ], ConfigurationModule.prototype, "_handleBootNotification", null);
607
+ __decorate([
608
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.Heartbeat),
609
+ __metadata("design:type", Function),
610
+ __metadata("design:paramtypes", [Object, Object]),
611
+ __metadata("design:returntype", Promise)
612
+ ], ConfigurationModule.prototype, "_handleHeartbeat", null);
613
+ __decorate([
614
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.NotifyDisplayMessages),
615
+ __metadata("design:type", Function),
616
+ __metadata("design:paramtypes", [Object, Object]),
617
+ __metadata("design:returntype", Promise)
618
+ ], ConfigurationModule.prototype, "_handleNotifyDisplayMessages", null);
619
+ __decorate([
620
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.FirmwareStatusNotification),
621
+ __metadata("design:type", Function),
622
+ __metadata("design:paramtypes", [Object, Object]),
623
+ __metadata("design:returntype", Promise)
624
+ ], ConfigurationModule.prototype, "_handleFirmwareStatusNotification", null);
625
+ __decorate([
626
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.DataTransfer),
627
+ __metadata("design:type", Function),
628
+ __metadata("design:paramtypes", [Object, Object]),
629
+ __metadata("design:returntype", Promise)
630
+ ], ConfigurationModule.prototype, "_handleDataTransfer", null);
631
+ __decorate([
632
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.ChangeAvailability),
633
+ __metadata("design:type", Function),
634
+ __metadata("design:paramtypes", [Object, Object]),
635
+ __metadata("design:returntype", void 0)
636
+ ], ConfigurationModule.prototype, "_handleChangeAvailability", null);
637
+ __decorate([
638
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.SetNetworkProfile),
639
+ __metadata("design:type", Function),
640
+ __metadata("design:paramtypes", [Object, Object]),
641
+ __metadata("design:returntype", Promise)
642
+ ], ConfigurationModule.prototype, "_handleSetNetworkProfile", null);
643
+ __decorate([
644
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.GetDisplayMessages),
645
+ __metadata("design:type", Function),
646
+ __metadata("design:paramtypes", [Object, Object]),
647
+ __metadata("design:returntype", void 0)
648
+ ], ConfigurationModule.prototype, "_handleGetDisplayMessages", null);
649
+ __decorate([
650
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.SetDisplayMessage),
651
+ __metadata("design:type", Function),
652
+ __metadata("design:paramtypes", [Object, Object]),
653
+ __metadata("design:returntype", Promise)
654
+ ], ConfigurationModule.prototype, "_handleSetDisplayMessage", null);
655
+ __decorate([
656
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.PublishFirmware),
657
+ __metadata("design:type", Function),
658
+ __metadata("design:paramtypes", [Object, Object]),
659
+ __metadata("design:returntype", void 0)
660
+ ], ConfigurationModule.prototype, "_handlePublishFirmware", null);
661
+ __decorate([
662
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.UnpublishFirmware),
663
+ __metadata("design:type", Function),
664
+ __metadata("design:paramtypes", [Object, Object]),
665
+ __metadata("design:returntype", void 0)
666
+ ], ConfigurationModule.prototype, "_handleUnpublishFirmware", null);
667
+ __decorate([
668
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.UpdateFirmware),
669
+ __metadata("design:type", Function),
670
+ __metadata("design:paramtypes", [Object, Object]),
671
+ __metadata("design:returntype", void 0)
672
+ ], ConfigurationModule.prototype, "_handleUpdateFirmware", null);
673
+ __decorate([
674
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.Reset),
675
+ __metadata("design:type", Function),
676
+ __metadata("design:paramtypes", [Object, Object]),
677
+ __metadata("design:returntype", void 0)
678
+ ], ConfigurationModule.prototype, "_handleReset", null);
679
+ __decorate([
680
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.TriggerMessage),
681
+ __metadata("design:type", Function),
682
+ __metadata("design:paramtypes", [Object, Object]),
683
+ __metadata("design:returntype", void 0)
684
+ ], ConfigurationModule.prototype, "_handleTriggerMessage", null);
685
+ __decorate([
686
+ AsHandler(OCPPVersion.OCPP2_0_1, OCPP2_0_1_CallAction.ClearDisplayMessage),
687
+ __metadata("design:type", Function),
688
+ __metadata("design:paramtypes", [Object, Object]),
689
+ __metadata("design:returntype", Promise)
690
+ ], ConfigurationModule.prototype, "_handleClearDisplayMessage", null);
691
+ __decorate([
692
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.Heartbeat),
693
+ __metadata("design:type", Function),
694
+ __metadata("design:paramtypes", [Object, Object]),
695
+ __metadata("design:returntype", Promise)
696
+ ], ConfigurationModule.prototype, "_handle16Heartbeat", null);
697
+ __decorate([
698
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.BootNotification),
699
+ __metadata("design:type", Function),
700
+ __metadata("design:paramtypes", [Object, Object]),
701
+ __metadata("design:returntype", Promise)
702
+ ], ConfigurationModule.prototype, "_handleOcpp16BootNotification", null);
703
+ __decorate([
704
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.GetConfiguration),
705
+ __metadata("design:type", Function),
706
+ __metadata("design:paramtypes", [Object, Object]),
707
+ __metadata("design:returntype", Promise)
708
+ ], ConfigurationModule.prototype, "_handleOcpp16GetConfiguration", null);
709
+ __decorate([
710
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.ChangeConfiguration),
711
+ __metadata("design:type", Function),
712
+ __metadata("design:paramtypes", [Object, Object]),
713
+ __metadata("design:returntype", Promise)
714
+ ], ConfigurationModule.prototype, "_handleOcpp16ChangeConfiguration", null);
715
+ __decorate([
716
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.TriggerMessage),
717
+ __metadata("design:type", Function),
718
+ __metadata("design:paramtypes", [Object, Object]),
719
+ __metadata("design:returntype", void 0)
720
+ ], ConfigurationModule.prototype, "_handleOcpp16TriggerMessage", null);
721
+ __decorate([
722
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.Reset),
723
+ __metadata("design:type", Function),
724
+ __metadata("design:paramtypes", [Object, Object]),
725
+ __metadata("design:returntype", void 0)
726
+ ], ConfigurationModule.prototype, "_handle16Reset", null);
727
+ __decorate([
728
+ AsHandler(OCPPVersion.OCPP1_6, OCPP1_6_CallAction.ChangeAvailability),
729
+ __metadata("design:type", Function),
730
+ __metadata("design:paramtypes", [Object, Object]),
731
+ __metadata("design:returntype", void 0)
732
+ ], ConfigurationModule.prototype, "_handleOcpp16ChangeAvailability", null);
733
+ //# sourceMappingURL=module.js.map