Commit | Line | Data |
---|---|---|
17ac262c JB |
1 | import { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile'; |
2 | import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile'; | |
492cf6ab JB |
3 | import ChargingStationTemplate, { |
4 | AmpereUnits, | |
5 | CurrentType, | |
6 | Voltage, | |
7 | } from '../types/ChargingStationTemplate'; | |
8 | import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues'; | |
17ac262c JB |
9 | |
10 | import { BootNotificationRequest } from '../types/ocpp/Requests'; | |
492cf6ab JB |
11 | import ChargingStation from './ChargingStation'; |
12 | import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils'; | |
17ac262c JB |
13 | import ChargingStationInfo from '../types/ChargingStationInfo'; |
14 | import Configuration from '../utils/Configuration'; | |
15 | import Constants from '../utils/Constants'; | |
fa7bccf4 JB |
16 | import { FileType } from '../types/FileType'; |
17 | import FileUtils from '../utils/FileUtils'; | |
492cf6ab JB |
18 | import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates'; |
19 | import { StandardParametersKey } from '../types/ocpp/Configuration'; | |
17ac262c JB |
20 | import Utils from '../utils/Utils'; |
21 | import { WebSocketCloseEventStatusString } from '../types/WebSocket'; | |
22 | import { WorkerProcessType } from '../types/Worker'; | |
23 | import crypto from 'crypto'; | |
fa7bccf4 | 24 | import fs from 'fs'; |
17ac262c JB |
25 | import logger from '../utils/Logger'; |
26 | import moment from 'moment'; | |
fa7bccf4 | 27 | import path from 'path'; |
17ac262c JB |
28 | |
29 | export class ChargingStationUtils { | |
30 | public static getChargingStationId( | |
31 | index: number, | |
32 | stationTemplate: ChargingStationTemplate | |
33 | ): string { | |
34 | // In case of multiple instances: add instance index to charging station id | |
35 | const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0; | |
36 | const idSuffix = stationTemplate.nameSuffix ?? ''; | |
37 | const idStr = '000000000' + index.toString(); | |
ccb1d6e9 | 38 | return stationTemplate?.fixedName |
17ac262c JB |
39 | ? stationTemplate.baseName |
40 | : stationTemplate.baseName + | |
41 | '-' + | |
42 | instanceIndex.toString() + | |
43 | idStr.substring(idStr.length - 4) + | |
44 | idSuffix; | |
45 | } | |
46 | ||
fa7bccf4 | 47 | public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string { |
17ac262c | 48 | const hashBootNotificationRequest = { |
fa7bccf4 JB |
49 | chargePointModel: stationTemplate.chargePointModel, |
50 | chargePointVendor: stationTemplate.chargePointVendor, | |
51 | ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && { | |
52 | chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix, | |
17ac262c | 53 | }), |
fa7bccf4 JB |
54 | ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && { |
55 | chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix, | |
17ac262c | 56 | }), |
fa7bccf4 JB |
57 | ...(!Utils.isUndefined(stationTemplate.firmwareVersion) && { |
58 | firmwareVersion: stationTemplate.firmwareVersion, | |
17ac262c | 59 | }), |
fa7bccf4 JB |
60 | ...(!Utils.isUndefined(stationTemplate.iccid) && { iccid: stationTemplate.iccid }), |
61 | ...(!Utils.isUndefined(stationTemplate.imsi) && { imsi: stationTemplate.imsi }), | |
62 | ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && { | |
63 | meterSerialNumber: stationTemplate.meterSerialNumberPrefix, | |
17ac262c | 64 | }), |
fa7bccf4 JB |
65 | ...(!Utils.isUndefined(stationTemplate.meterType) && { |
66 | meterType: stationTemplate.meterType, | |
17ac262c JB |
67 | }), |
68 | }; | |
69 | return crypto | |
70 | .createHash(Constants.DEFAULT_HASH_ALGORITHM) | |
fa7bccf4 JB |
71 | .update( |
72 | JSON.stringify(hashBootNotificationRequest) + | |
73 | ChargingStationUtils.getChargingStationId(index, stationTemplate) | |
74 | ) | |
17ac262c JB |
75 | .digest('hex'); |
76 | } | |
77 | ||
fa7bccf4 JB |
78 | public static getTemplateMaxNumberOfConnectors(stationTemplate: ChargingStationTemplate): number { |
79 | const templateConnectors = stationTemplate?.Connectors; | |
80 | if (!templateConnectors) { | |
81 | return -1; | |
82 | } | |
83 | return Object.keys(templateConnectors).length; | |
84 | } | |
85 | ||
86 | public static checkTemplateMaxConnectors( | |
87 | templateMaxConnectors: number, | |
88 | templateFile: string, | |
89 | logPrefix: string | |
90 | ): void { | |
91 | if (templateMaxConnectors === 0) { | |
92 | logger.warn( | |
93 | `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration` | |
94 | ); | |
95 | } else if (templateMaxConnectors < 0) { | |
96 | logger.error( | |
97 | `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined` | |
98 | ); | |
99 | } | |
100 | } | |
101 | ||
102 | public static getConfiguredNumberOfConnectors( | |
103 | index: number, | |
104 | stationTemplate: ChargingStationTemplate | |
105 | ): number { | |
106 | let configuredMaxConnectors: number; | |
107 | if (!Utils.isEmptyArray(stationTemplate.numberOfConnectors)) { | |
108 | const numberOfConnectors = stationTemplate.numberOfConnectors as number[]; | |
109 | // Distribute evenly the number of connectors | |
110 | configuredMaxConnectors = numberOfConnectors[(index - 1) % numberOfConnectors.length]; | |
111 | } else if (!Utils.isUndefined(stationTemplate.numberOfConnectors)) { | |
112 | configuredMaxConnectors = stationTemplate.numberOfConnectors as number; | |
113 | } else { | |
114 | configuredMaxConnectors = stationTemplate?.Connectors[0] | |
115 | ? ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate) - 1 | |
116 | : ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate); | |
117 | } | |
118 | return configuredMaxConnectors; | |
119 | } | |
120 | ||
121 | public static checkConfiguredMaxConnectors( | |
122 | configuredMaxConnectors: number, | |
123 | templateFile: string, | |
124 | logPrefix: string | |
125 | ): void { | |
126 | if (configuredMaxConnectors <= 0) { | |
127 | logger.warn( | |
128 | `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors` | |
129 | ); | |
130 | } | |
131 | } | |
132 | ||
17ac262c JB |
133 | public static createBootNotificationRequest( |
134 | stationInfo: ChargingStationInfo | |
135 | ): BootNotificationRequest { | |
136 | return { | |
137 | chargePointModel: stationInfo.chargePointModel, | |
138 | chargePointVendor: stationInfo.chargePointVendor, | |
139 | ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && { | |
140 | chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber, | |
141 | }), | |
142 | ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && { | |
143 | chargePointSerialNumber: stationInfo.chargePointSerialNumber, | |
144 | }), | |
145 | ...(!Utils.isUndefined(stationInfo.firmwareVersion) && { | |
146 | firmwareVersion: stationInfo.firmwareVersion, | |
147 | }), | |
148 | ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }), | |
149 | ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }), | |
150 | ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && { | |
151 | meterSerialNumber: stationInfo.meterSerialNumber, | |
152 | }), | |
153 | ...(!Utils.isUndefined(stationInfo.meterType) && { | |
154 | meterType: stationInfo.meterType, | |
155 | }), | |
156 | }; | |
157 | } | |
158 | ||
159 | public static workerPoolInUse(): boolean { | |
160 | return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes( | |
161 | Configuration.getWorkerProcess() | |
162 | ); | |
163 | } | |
164 | ||
165 | public static workerDynamicPoolInUse(): boolean { | |
166 | return Configuration.getWorkerProcess() === WorkerProcessType.DYNAMIC_POOL; | |
167 | } | |
168 | ||
169 | /** | |
170 | * Convert websocket error code to human readable string message | |
171 | * | |
172 | * @param code websocket error code | |
173 | * @returns human readable string message | |
174 | */ | |
175 | public static getWebSocketCloseEventStatusString(code: number): string { | |
176 | if (code >= 0 && code <= 999) { | |
177 | return '(Unused)'; | |
178 | } else if (code >= 1016) { | |
179 | if (code <= 1999) { | |
180 | return '(For WebSocket standard)'; | |
181 | } else if (code <= 2999) { | |
182 | return '(For WebSocket extensions)'; | |
183 | } else if (code <= 3999) { | |
184 | return '(For libraries and frameworks)'; | |
185 | } else if (code <= 4999) { | |
186 | return '(For applications)'; | |
187 | } | |
188 | } | |
189 | if (!Utils.isUndefined(WebSocketCloseEventStatusString[code])) { | |
190 | return WebSocketCloseEventStatusString[code] as string; | |
191 | } | |
192 | return '(Unknown)'; | |
193 | } | |
194 | ||
195 | public static warnDeprecatedTemplateKey( | |
196 | template: ChargingStationTemplate, | |
197 | key: string, | |
198 | templateFile: string, | |
199 | logPrefix: string, | |
200 | logMsgToAppend = '' | |
201 | ): void { | |
202 | if (!Utils.isUndefined(template[key])) { | |
17ac262c JB |
203 | logger.warn( |
204 | `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${ | |
205 | logMsgToAppend && '. ' + logMsgToAppend | |
206 | }` | |
207 | ); | |
208 | } | |
209 | } | |
210 | ||
211 | public static convertDeprecatedTemplateKey( | |
212 | template: ChargingStationTemplate, | |
213 | deprecatedKey: string, | |
214 | key: string | |
215 | ): void { | |
216 | if (!Utils.isUndefined(template[deprecatedKey])) { | |
217 | template[key] = template[deprecatedKey] as unknown; | |
218 | delete template[deprecatedKey]; | |
219 | } | |
220 | } | |
221 | ||
fa7bccf4 JB |
222 | public static stationTemplateToStationInfo( |
223 | stationTemplate: ChargingStationTemplate | |
224 | ): ChargingStationInfo { | |
225 | stationTemplate = Utils.cloneObject(stationTemplate); | |
226 | delete stationTemplate.power; | |
227 | delete stationTemplate.powerUnit; | |
228 | delete stationTemplate.Configuration; | |
229 | delete stationTemplate.AutomaticTransactionGenerator; | |
230 | delete stationTemplate.chargeBoxSerialNumberPrefix; | |
231 | delete stationTemplate.chargePointSerialNumberPrefix; | |
232 | return stationTemplate; | |
233 | } | |
234 | ||
235 | public static createStationInfoHash(stationInfo: ChargingStationInfo): void { | |
ccb1d6e9 JB |
236 | const previousInfoHash = stationInfo?.infoHash ?? ''; |
237 | delete stationInfo.infoHash; | |
238 | const currentInfoHash = crypto | |
239 | .createHash(Constants.DEFAULT_HASH_ALGORITHM) | |
240 | .update(JSON.stringify(stationInfo)) | |
241 | .digest('hex'); | |
242 | if ( | |
243 | Utils.isEmptyString(previousInfoHash) || | |
244 | (!Utils.isEmptyString(previousInfoHash) && currentInfoHash !== previousInfoHash) | |
245 | ) { | |
246 | stationInfo.infoHash = currentInfoHash; | |
247 | } else { | |
248 | stationInfo.infoHash = previousInfoHash; | |
17ac262c | 249 | } |
17ac262c JB |
250 | } |
251 | ||
252 | public static createSerialNumber( | |
fa7bccf4 | 253 | stationTemplate: ChargingStationTemplate, |
17ac262c | 254 | stationInfo: ChargingStationInfo, |
fa7bccf4 JB |
255 | params: { |
256 | randomSerialNumberUpperCase?: boolean; | |
257 | randomSerialNumber?: boolean; | |
258 | } = { | |
17ac262c JB |
259 | randomSerialNumberUpperCase: true, |
260 | randomSerialNumber: true, | |
261 | } | |
262 | ): void { | |
263 | params = params ?? {}; | |
264 | params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true; | |
265 | params.randomSerialNumber = params?.randomSerialNumber ?? true; | |
fa7bccf4 JB |
266 | const serialNumberSuffix = params?.randomSerialNumber |
267 | ? ChargingStationUtils.getRandomSerialNumberSuffix({ | |
268 | upperCase: params.randomSerialNumberUpperCase, | |
269 | }) | |
270 | : ''; | |
271 | stationTemplate?.chargePointSerialNumberPrefix && | |
272 | stationInfo && | |
273 | Utils.isNullOrUndefined(stationInfo?.chargePointSerialNumber) | |
274 | ? (stationInfo.chargePointSerialNumber = | |
275 | stationTemplate.chargePointSerialNumberPrefix + serialNumberSuffix) | |
276 | : stationInfo && delete stationInfo.chargePointSerialNumber; | |
277 | stationTemplate?.chargeBoxSerialNumberPrefix && | |
278 | stationInfo && | |
279 | Utils.isNullOrUndefined(stationInfo?.chargeBoxSerialNumber) | |
280 | ? (stationInfo.chargeBoxSerialNumber = | |
281 | stationTemplate.chargeBoxSerialNumberPrefix + serialNumberSuffix) | |
282 | : stationInfo && delete stationInfo.chargeBoxSerialNumber; | |
283 | stationTemplate?.meterSerialNumberPrefix && | |
284 | stationInfo && | |
285 | Utils.isNullOrUndefined(stationInfo?.meterSerialNumber) | |
286 | ? (stationInfo.meterSerialNumber = | |
287 | stationTemplate.meterSerialNumberPrefix + serialNumberSuffix) | |
288 | : stationInfo && delete stationInfo.meterSerialNumber; | |
17ac262c JB |
289 | } |
290 | ||
291 | public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number { | |
292 | let unitDivider = 1; | |
293 | switch (stationInfo.amperageLimitationUnit) { | |
294 | case AmpereUnits.DECI_AMPERE: | |
295 | unitDivider = 10; | |
296 | break; | |
297 | case AmpereUnits.CENTI_AMPERE: | |
298 | unitDivider = 100; | |
299 | break; | |
300 | case AmpereUnits.MILLI_AMPERE: | |
301 | unitDivider = 1000; | |
302 | break; | |
303 | } | |
304 | return unitDivider; | |
305 | } | |
306 | ||
307 | /** | |
308 | * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority) | |
309 | * | |
310 | * @param {ChargingProfile[]} chargingProfiles | |
311 | * @param {string} logPrefix | |
312 | * @returns {{ limit, matchingChargingProfile }} | |
313 | */ | |
314 | public static getLimitFromChargingProfiles( | |
315 | chargingProfiles: ChargingProfile[], | |
316 | logPrefix: string | |
317 | ): { | |
318 | limit: number; | |
319 | matchingChargingProfile: ChargingProfile; | |
320 | } | null { | |
321 | for (const chargingProfile of chargingProfiles) { | |
322 | // Set helpers | |
323 | const currentMoment = moment(); | |
324 | const chargingSchedule = chargingProfile.chargingSchedule; | |
325 | // Check type (recurring) and if it is already active | |
326 | // Adjust the daily recurring schedule to today | |
327 | if ( | |
328 | chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && | |
329 | chargingProfile.recurrencyKind === RecurrencyKindType.DAILY && | |
330 | currentMoment.isAfter(chargingSchedule.startSchedule) | |
331 | ) { | |
332 | const currentDate = new Date(); | |
333 | chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule); | |
334 | chargingSchedule.startSchedule.setFullYear( | |
335 | currentDate.getFullYear(), | |
336 | currentDate.getMonth(), | |
337 | currentDate.getDate() | |
338 | ); | |
339 | // Check if the start of the schedule is yesterday | |
340 | if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) { | |
341 | chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1); | |
342 | } | |
343 | } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) { | |
344 | return null; | |
345 | } | |
346 | // Check if the charging profile is active | |
347 | if ( | |
348 | moment(chargingSchedule.startSchedule) | |
349 | .add(chargingSchedule.duration, 's') | |
350 | .isAfter(currentMoment) | |
351 | ) { | |
352 | let lastButOneSchedule: ChargingSchedulePeriod; | |
353 | // Search the right schedule period | |
354 | for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) { | |
355 | // Handling of only one period | |
356 | if ( | |
357 | chargingSchedule.chargingSchedulePeriod.length === 1 && | |
358 | schedulePeriod.startPeriod === 0 | |
359 | ) { | |
360 | const result = { | |
361 | limit: schedulePeriod.limit, | |
362 | matchingChargingProfile: chargingProfile, | |
363 | }; | |
364 | logger.debug( | |
365 | `${logPrefix} Matching charging profile found for power limitation: %j`, | |
366 | result | |
367 | ); | |
368 | return result; | |
369 | } | |
370 | // Find the right schedule period | |
371 | if ( | |
372 | moment(chargingSchedule.startSchedule) | |
373 | .add(schedulePeriod.startPeriod, 's') | |
374 | .isAfter(currentMoment) | |
375 | ) { | |
376 | // Found the schedule: last but one is the correct one | |
377 | const result = { | |
378 | limit: lastButOneSchedule.limit, | |
379 | matchingChargingProfile: chargingProfile, | |
380 | }; | |
381 | logger.debug( | |
382 | `${logPrefix} Matching charging profile found for power limitation: %j`, | |
383 | result | |
384 | ); | |
385 | return result; | |
386 | } | |
387 | // Keep it | |
388 | lastButOneSchedule = schedulePeriod; | |
389 | // Handle the last schedule period | |
390 | if ( | |
391 | schedulePeriod.startPeriod === | |
392 | chargingSchedule.chargingSchedulePeriod[ | |
393 | chargingSchedule.chargingSchedulePeriod.length - 1 | |
394 | ].startPeriod | |
395 | ) { | |
396 | const result = { | |
397 | limit: lastButOneSchedule.limit, | |
398 | matchingChargingProfile: chargingProfile, | |
399 | }; | |
400 | logger.debug( | |
401 | `${logPrefix} Matching charging profile found for power limitation: %j`, | |
402 | result | |
403 | ); | |
404 | return result; | |
405 | } | |
406 | } | |
407 | } | |
408 | } | |
409 | return null; | |
410 | } | |
411 | ||
492cf6ab JB |
412 | public static getDefaultVoltageOut( |
413 | currentType: CurrentType, | |
414 | templateFile: string, | |
415 | logPrefix: string | |
416 | ): Voltage { | |
417 | const errMsg = `${logPrefix} Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`; | |
418 | let defaultVoltageOut: number; | |
419 | switch (currentType) { | |
420 | case CurrentType.AC: | |
421 | defaultVoltageOut = Voltage.VOLTAGE_230; | |
422 | break; | |
423 | case CurrentType.DC: | |
424 | defaultVoltageOut = Voltage.VOLTAGE_400; | |
425 | break; | |
426 | default: | |
427 | logger.error(errMsg); | |
428 | throw new Error(errMsg); | |
429 | } | |
430 | return defaultVoltageOut; | |
431 | } | |
432 | ||
433 | public static getSampledValueTemplate( | |
434 | chargingStation: ChargingStation, | |
435 | connectorId: number, | |
436 | measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, | |
437 | phase?: MeterValuePhase | |
438 | ): SampledValueTemplate | undefined { | |
439 | const onPhaseStr = phase ? `on phase ${phase} ` : ''; | |
440 | if (!Constants.SUPPORTED_MEASURANDS.includes(measurand)) { | |
441 | logger.warn( | |
442 | `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}` | |
443 | ); | |
444 | return; | |
445 | } | |
446 | if ( | |
447 | measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && | |
448 | !ChargingStationConfigurationUtils.getConfigurationKey( | |
449 | chargingStation, | |
450 | StandardParametersKey.MeterValuesSampledData | |
451 | )?.value.includes(measurand) | |
452 | ) { | |
453 | logger.debug( | |
454 | `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${ | |
455 | StandardParametersKey.MeterValuesSampledData | |
456 | }' OCPP parameter` | |
457 | ); | |
458 | return; | |
459 | } | |
460 | const sampledValueTemplates: SampledValueTemplate[] = | |
461 | chargingStation.getConnectorStatus(connectorId).MeterValues; | |
462 | for ( | |
463 | let index = 0; | |
464 | !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length; | |
465 | index++ | |
466 | ) { | |
467 | if ( | |
468 | !Constants.SUPPORTED_MEASURANDS.includes( | |
469 | sampledValueTemplates[index]?.measurand ?? | |
470 | MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
471 | ) | |
472 | ) { | |
473 | logger.warn( | |
474 | `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}` | |
475 | ); | |
476 | } else if ( | |
477 | phase && | |
478 | sampledValueTemplates[index]?.phase === phase && | |
479 | sampledValueTemplates[index]?.measurand === measurand && | |
480 | ChargingStationConfigurationUtils.getConfigurationKey( | |
481 | chargingStation, | |
482 | StandardParametersKey.MeterValuesSampledData | |
483 | )?.value.includes(measurand) | |
484 | ) { | |
485 | return sampledValueTemplates[index]; | |
486 | } else if ( | |
487 | !phase && | |
488 | !sampledValueTemplates[index].phase && | |
489 | sampledValueTemplates[index]?.measurand === measurand && | |
490 | ChargingStationConfigurationUtils.getConfigurationKey( | |
491 | chargingStation, | |
492 | StandardParametersKey.MeterValuesSampledData | |
493 | )?.value.includes(measurand) | |
494 | ) { | |
495 | return sampledValueTemplates[index]; | |
496 | } else if ( | |
497 | measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER && | |
498 | (!sampledValueTemplates[index].measurand || | |
499 | sampledValueTemplates[index].measurand === measurand) | |
500 | ) { | |
501 | return sampledValueTemplates[index]; | |
502 | } | |
503 | } | |
504 | if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { | |
505 | const errorMsg = `${chargingStation.logPrefix()} Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`; | |
506 | logger.error(errorMsg); | |
507 | throw new Error(errorMsg); | |
508 | } | |
509 | logger.debug( | |
510 | `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}` | |
511 | ); | |
512 | } | |
513 | ||
fa7bccf4 JB |
514 | public static getAuthorizedTags( |
515 | stationInfo: ChargingStationInfo, | |
516 | templateFile: string, | |
517 | logPrefix: string | |
518 | ): string[] { | |
519 | let authorizedTags: string[] = []; | |
520 | const authorizationFile = ChargingStationUtils.getAuthorizationFile(stationInfo); | |
521 | if (authorizationFile) { | |
522 | try { | |
523 | // Load authorization file | |
524 | authorizedTags = JSON.parse(fs.readFileSync(authorizationFile, 'utf8')) as string[]; | |
525 | } catch (error) { | |
526 | FileUtils.handleFileException( | |
527 | logPrefix, | |
528 | FileType.Authorization, | |
529 | authorizationFile, | |
530 | error as NodeJS.ErrnoException | |
531 | ); | |
532 | } | |
533 | } else { | |
534 | logger.info(logPrefix + ' No authorization file given in template file ' + templateFile); | |
535 | } | |
536 | return authorizedTags; | |
537 | } | |
538 | ||
539 | public static getAuthorizationFile(stationInfo: ChargingStationInfo): string | undefined { | |
540 | return ( | |
541 | stationInfo.authorizationFile && | |
542 | path.join( | |
543 | path.resolve(__dirname, '../'), | |
544 | 'assets', | |
545 | path.basename(stationInfo.authorizationFile) | |
546 | ) | |
547 | ); | |
548 | } | |
549 | ||
17ac262c JB |
550 | private static getRandomSerialNumberSuffix(params?: { |
551 | randomBytesLength?: number; | |
552 | upperCase?: boolean; | |
553 | }): string { | |
554 | const randomSerialNumberSuffix = crypto | |
555 | .randomBytes(params?.randomBytesLength ?? 16) | |
556 | .toString('hex'); | |
557 | if (params?.upperCase) { | |
558 | return randomSerialNumberSuffix.toUpperCase(); | |
559 | } | |
560 | return randomSerialNumberSuffix; | |
561 | } | |
562 | } |