Fix remaining linter errors properly
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16RequestService.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
4 import {
5 AuthorizeRequest,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionReason,
9 OCPP16StopTransactionResponse,
10 StartTransactionRequest,
11 StopTransactionRequest,
12 } from '../../../types/ocpp/1.6/Transaction';
13 import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
14 import {
15 DiagnosticsStatusNotificationRequest,
16 HeartbeatRequest,
17 OCPP16BootNotificationRequest,
18 OCPP16RequestCommand,
19 StatusNotificationRequest,
20 } from '../../../types/ocpp/1.6/Requests';
21 import MeasurandPerPhaseSampledValueTemplates, {
22 SampledValueTemplate,
23 } from '../../../types/MeasurandPerPhaseSampledValueTemplates';
24 import {
25 MeterValueUnit,
26 MeterValuesRequest,
27 OCPP16MeterValue,
28 OCPP16MeterValueMeasurand,
29 OCPP16MeterValuePhase,
30 } from '../../../types/ocpp/1.6/MeterValues';
31
32 import type ChargingStation from '../../ChargingStation';
33 import Constants from '../../../utils/Constants';
34 import { ErrorType } from '../../../types/ocpp/ErrorType';
35 import MeasurandValues from '../../../types/MeasurandValues';
36 import { OCPP16BootNotificationResponse } from '../../../types/ocpp/1.6/Responses';
37 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
38 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
39 import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
40 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
41 import OCPPError from '../../../exception/OCPPError';
42 import OCPPRequestService from '../OCPPRequestService';
43 import type OCPPResponseService from '../OCPPResponseService';
44 import { SendParams } from '../../../types/ocpp/Requests';
45 import Utils from '../../../utils/Utils';
46 import logger from '../../../utils/Logger';
47
48 const moduleName = 'OCPP16RequestService';
49
50 export default class OCPP16RequestService extends OCPPRequestService {
51 public constructor(chargingStation: ChargingStation, ocppResponseService: OCPPResponseService) {
52 if (new.target?.name === moduleName) {
53 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
54 }
55 super(chargingStation, ocppResponseService);
56 }
57
58 public async sendHeartbeat(params?: SendParams): Promise<void> {
59 const payload: HeartbeatRequest = {};
60 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.HEARTBEAT, params);
61 }
62
63 public async sendBootNotification(
64 chargePointModel: string,
65 chargePointVendor: string,
66 chargeBoxSerialNumber?: string,
67 firmwareVersion?: string,
68 chargePointSerialNumber?: string,
69 iccid?: string,
70 imsi?: string,
71 meterSerialNumber?: string,
72 meterType?: string,
73 params?: SendParams
74 ): Promise<OCPP16BootNotificationResponse> {
75 const payload: OCPP16BootNotificationRequest = {
76 chargePointModel,
77 chargePointVendor,
78 ...(!Utils.isUndefined(chargeBoxSerialNumber) && { chargeBoxSerialNumber }),
79 ...(!Utils.isUndefined(chargePointSerialNumber) && { chargePointSerialNumber }),
80 ...(!Utils.isUndefined(firmwareVersion) && { firmwareVersion }),
81 ...(!Utils.isUndefined(iccid) && { iccid }),
82 ...(!Utils.isUndefined(imsi) && { imsi }),
83 ...(!Utils.isUndefined(meterSerialNumber) && { meterSerialNumber }),
84 ...(!Utils.isUndefined(meterType) && { meterType }),
85 };
86 return (await this.sendMessage(
87 Utils.generateUUID(),
88 payload,
89 OCPP16RequestCommand.BOOT_NOTIFICATION,
90 { ...params, skipBufferingOnError: true }
91 )) as OCPP16BootNotificationResponse;
92 }
93
94 public async sendStatusNotification(
95 connectorId: number,
96 status: OCPP16ChargePointStatus,
97 errorCode: OCPP16ChargePointErrorCode = OCPP16ChargePointErrorCode.NO_ERROR
98 ): Promise<void> {
99 const payload: StatusNotificationRequest = {
100 connectorId,
101 errorCode,
102 status,
103 };
104 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.STATUS_NOTIFICATION);
105 }
106
107 public async sendAuthorize(
108 connectorId: number,
109 idTag?: string
110 ): Promise<OCPP16AuthorizeResponse> {
111 const payload: AuthorizeRequest = {
112 ...(!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.DEFAULT_IDTAG }),
113 };
114 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag = idTag;
115 return (await this.sendMessage(
116 Utils.generateUUID(),
117 payload,
118 OCPP16RequestCommand.AUTHORIZE
119 )) as OCPP16AuthorizeResponse;
120 }
121
122 public async sendStartTransaction(
123 connectorId: number,
124 idTag?: string
125 ): Promise<OCPP16StartTransactionResponse> {
126 const payload: StartTransactionRequest = {
127 connectorId,
128 ...(!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.DEFAULT_IDTAG }),
129 meterStart: this.chargingStation.getEnergyActiveImportRegisterByConnectorId(connectorId),
130 timestamp: new Date().toISOString(),
131 };
132 return (await this.sendMessage(
133 Utils.generateUUID(),
134 payload,
135 OCPP16RequestCommand.START_TRANSACTION
136 )) as OCPP16StartTransactionResponse;
137 }
138
139 public async sendStopTransaction(
140 transactionId: number,
141 meterStop: number,
142 idTag?: string,
143 reason: OCPP16StopTransactionReason = OCPP16StopTransactionReason.NONE
144 ): Promise<OCPP16StopTransactionResponse> {
145 let connectorId: number;
146 for (const id of this.chargingStation.connectors.keys()) {
147 if (id > 0 && this.chargingStation.getConnectorStatus(id)?.transactionId === transactionId) {
148 connectorId = id;
149 break;
150 }
151 }
152 const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
153 this.chargingStation,
154 connectorId,
155 meterStop
156 );
157 // FIXME: should be a callback, each OCPP commands implementation must do only one job
158 this.chargingStation.getBeginEndMeterValues() &&
159 this.chargingStation.getOcppStrictCompliance() &&
160 !this.chargingStation.getOutOfOrderEndMeterValues() &&
161 (await this.sendTransactionEndMeterValues(
162 connectorId,
163 transactionId,
164 transactionEndMeterValue
165 ));
166 const payload: StopTransactionRequest = {
167 transactionId,
168 ...(!Utils.isUndefined(idTag) && { idTag }),
169 meterStop,
170 timestamp: new Date().toISOString(),
171 ...(reason && { reason }),
172 ...(this.chargingStation.getTransactionDataMeterValues() && {
173 transactionData: OCPP16ServiceUtils.buildTransactionDataMeterValues(
174 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue,
175 transactionEndMeterValue
176 ),
177 }),
178 };
179 return (await this.sendMessage(
180 Utils.generateUUID(),
181 payload,
182 OCPP16RequestCommand.STOP_TRANSACTION
183 )) as OCPP16StartTransactionResponse;
184 }
185
186 public async sendMeterValues(
187 connectorId: number,
188 transactionId: number,
189 interval: number,
190 debug = false
191 ): Promise<void> {
192 const meterValue: OCPP16MeterValue = {
193 timestamp: new Date().toISOString(),
194 sampledValue: [],
195 };
196 const connector = this.chargingStation.getConnectorStatus(connectorId);
197 // SoC measurand
198 const socSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
199 connectorId,
200 OCPP16MeterValueMeasurand.STATE_OF_CHARGE
201 );
202 if (socSampledValueTemplate) {
203 const socSampledValueTemplateValue = socSampledValueTemplate.value
204 ? Utils.getRandomFloatFluctuatedRounded(
205 parseInt(socSampledValueTemplate.value),
206 socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
207 )
208 : Utils.getRandomInteger(100);
209 meterValue.sampledValue.push(
210 OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
211 );
212 const sampledValuesIndex = meterValue.sampledValue.length - 1;
213 if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
214 logger.error(
215 `${this.chargingStation.logPrefix()} MeterValues measurand ${
216 meterValue.sampledValue[sampledValuesIndex].measurand ??
217 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
218 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
219 meterValue.sampledValue[sampledValuesIndex].value
220 }/100`
221 );
222 }
223 }
224 // Voltage measurand
225 const voltageSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
226 connectorId,
227 OCPP16MeterValueMeasurand.VOLTAGE
228 );
229 if (voltageSampledValueTemplate) {
230 const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value
231 ? parseInt(voltageSampledValueTemplate.value)
232 : this.chargingStation.getVoltageOut();
233 const fluctuationPercent =
234 voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
235 const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
236 voltageSampledValueTemplateValue,
237 fluctuationPercent
238 );
239 if (
240 this.chargingStation.getNumberOfPhases() !== 3 ||
241 (this.chargingStation.getNumberOfPhases() === 3 &&
242 this.chargingStation.getMainVoltageMeterValues())
243 ) {
244 meterValue.sampledValue.push(
245 OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue)
246 );
247 }
248 for (
249 let phase = 1;
250 this.chargingStation.getNumberOfPhases() === 3 &&
251 phase <= this.chargingStation.getNumberOfPhases();
252 phase++
253 ) {
254 const phaseLineToNeutralValue = `L${phase}-N`;
255 const voltagePhaseLineToNeutralSampledValueTemplate =
256 this.chargingStation.getSampledValueTemplate(
257 connectorId,
258 OCPP16MeterValueMeasurand.VOLTAGE,
259 phaseLineToNeutralValue as OCPP16MeterValuePhase
260 );
261 let voltagePhaseLineToNeutralMeasurandValue: number;
262 if (voltagePhaseLineToNeutralSampledValueTemplate) {
263 const voltagePhaseLineToNeutralSampledValueTemplateValue =
264 voltagePhaseLineToNeutralSampledValueTemplate.value
265 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
266 : this.chargingStation.getVoltageOut();
267 const fluctuationPhaseToNeutralPercent =
268 voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
269 Constants.DEFAULT_FLUCTUATION_PERCENT;
270 voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
271 voltagePhaseLineToNeutralSampledValueTemplateValue,
272 fluctuationPhaseToNeutralPercent
273 );
274 }
275 meterValue.sampledValue.push(
276 OCPP16ServiceUtils.buildSampledValue(
277 voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
278 voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
279 null,
280 phaseLineToNeutralValue as OCPP16MeterValuePhase
281 )
282 );
283 if (this.chargingStation.getPhaseLineToLineVoltageMeterValues()) {
284 const phaseLineToLineValue = `L${phase}-L${
285 (phase + 1) % this.chargingStation.getNumberOfPhases() !== 0
286 ? (phase + 1) % this.chargingStation.getNumberOfPhases()
287 : this.chargingStation.getNumberOfPhases()
288 }`;
289 const voltagePhaseLineToLineSampledValueTemplate =
290 this.chargingStation.getSampledValueTemplate(
291 connectorId,
292 OCPP16MeterValueMeasurand.VOLTAGE,
293 phaseLineToLineValue as OCPP16MeterValuePhase
294 );
295 let voltagePhaseLineToLineMeasurandValue: number;
296 if (voltagePhaseLineToLineSampledValueTemplate) {
297 const voltagePhaseLineToLineSampledValueTemplateValue =
298 voltagePhaseLineToLineSampledValueTemplate.value
299 ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
300 : Voltage.VOLTAGE_400;
301 const fluctuationPhaseLineToLinePercent =
302 voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
303 Constants.DEFAULT_FLUCTUATION_PERCENT;
304 voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
305 voltagePhaseLineToLineSampledValueTemplateValue,
306 fluctuationPhaseLineToLinePercent
307 );
308 }
309 const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
310 Voltage.VOLTAGE_400,
311 fluctuationPercent
312 );
313 meterValue.sampledValue.push(
314 OCPP16ServiceUtils.buildSampledValue(
315 voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
316 voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
317 null,
318 phaseLineToLineValue as OCPP16MeterValuePhase
319 )
320 );
321 }
322 }
323 }
324 // Power.Active.Import measurand
325 const powerSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
326 connectorId,
327 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
328 );
329 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
330 if (this.chargingStation.getNumberOfPhases() === 3) {
331 powerPerPhaseSampledValueTemplates = {
332 L1: this.chargingStation.getSampledValueTemplate(
333 connectorId,
334 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
335 OCPP16MeterValuePhase.L1_N
336 ),
337 L2: this.chargingStation.getSampledValueTemplate(
338 connectorId,
339 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
340 OCPP16MeterValuePhase.L2_N
341 ),
342 L3: this.chargingStation.getSampledValueTemplate(
343 connectorId,
344 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
345 OCPP16MeterValuePhase.L3_N
346 ),
347 };
348 }
349 if (powerSampledValueTemplate) {
350 OCPP16ServiceUtils.checkMeasurandPowerDivider(
351 this.chargingStation,
352 powerSampledValueTemplate.measurand
353 );
354 const errMsg = `${this.chargingStation.logPrefix()} MeterValues measurand ${
355 powerSampledValueTemplate.measurand ??
356 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
357 }: Unknown ${this.chargingStation.getCurrentOutType()} currentOutType in template file ${
358 this.chargingStation.stationTemplateFile
359 }, cannot calculate ${
360 powerSampledValueTemplate.measurand ??
361 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
362 } measurand value`;
363 const powerMeasurandValues = {} as MeasurandValues;
364 const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
365 const maxPower = Math.round(
366 this.chargingStation.stationInfo.maxPower / this.chargingStation.stationInfo.powerDivider
367 );
368 const maxPowerPerPhase = Math.round(
369 this.chargingStation.stationInfo.maxPower /
370 this.chargingStation.stationInfo.powerDivider /
371 this.chargingStation.getNumberOfPhases()
372 );
373 switch (this.chargingStation.getCurrentOutType()) {
374 case CurrentType.AC:
375 if (this.chargingStation.getNumberOfPhases() === 3) {
376 const defaultFluctuatedPowerPerPhase =
377 powerSampledValueTemplate.value &&
378 Utils.getRandomFloatFluctuatedRounded(
379 parseInt(powerSampledValueTemplate.value) /
380 this.chargingStation.getNumberOfPhases(),
381 powerSampledValueTemplate.fluctuationPercent ??
382 Constants.DEFAULT_FLUCTUATION_PERCENT
383 );
384 const phase1FluctuatedValue =
385 powerPerPhaseSampledValueTemplates?.L1?.value &&
386 Utils.getRandomFloatFluctuatedRounded(
387 parseInt(powerPerPhaseSampledValueTemplates.L1.value),
388 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
389 Constants.DEFAULT_FLUCTUATION_PERCENT
390 );
391 const phase2FluctuatedValue =
392 powerPerPhaseSampledValueTemplates?.L2?.value &&
393 Utils.getRandomFloatFluctuatedRounded(
394 parseInt(powerPerPhaseSampledValueTemplates.L2.value),
395 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
396 Constants.DEFAULT_FLUCTUATION_PERCENT
397 );
398 const phase3FluctuatedValue =
399 powerPerPhaseSampledValueTemplates?.L3?.value &&
400 Utils.getRandomFloatFluctuatedRounded(
401 parseInt(powerPerPhaseSampledValueTemplates.L3.value),
402 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
403 Constants.DEFAULT_FLUCTUATION_PERCENT
404 );
405 powerMeasurandValues.L1 =
406 phase1FluctuatedValue ??
407 defaultFluctuatedPowerPerPhase ??
408 Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
409 powerMeasurandValues.L2 =
410 phase2FluctuatedValue ??
411 defaultFluctuatedPowerPerPhase ??
412 Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
413 powerMeasurandValues.L3 =
414 phase3FluctuatedValue ??
415 defaultFluctuatedPowerPerPhase ??
416 Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
417 } else {
418 powerMeasurandValues.L1 = powerSampledValueTemplate.value
419 ? Utils.getRandomFloatFluctuatedRounded(
420 parseInt(powerSampledValueTemplate.value),
421 powerSampledValueTemplate.fluctuationPercent ??
422 Constants.DEFAULT_FLUCTUATION_PERCENT
423 )
424 : Utils.getRandomFloatRounded(maxPower / unitDivider);
425 powerMeasurandValues.L2 = 0;
426 powerMeasurandValues.L3 = 0;
427 }
428 powerMeasurandValues.allPhases = Utils.roundTo(
429 powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
430 2
431 );
432 break;
433 case CurrentType.DC:
434 powerMeasurandValues.allPhases = powerSampledValueTemplate.value
435 ? Utils.getRandomFloatFluctuatedRounded(
436 parseInt(powerSampledValueTemplate.value),
437 powerSampledValueTemplate.fluctuationPercent ??
438 Constants.DEFAULT_FLUCTUATION_PERCENT
439 )
440 : Utils.getRandomFloatRounded(maxPower / unitDivider);
441 break;
442 default:
443 logger.error(errMsg);
444 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
445 }
446 meterValue.sampledValue.push(
447 OCPP16ServiceUtils.buildSampledValue(
448 powerSampledValueTemplate,
449 powerMeasurandValues.allPhases
450 )
451 );
452 const sampledValuesIndex = meterValue.sampledValue.length - 1;
453 const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2);
454 if (
455 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPowerRounded ||
456 debug
457 ) {
458 logger.error(
459 `${this.chargingStation.logPrefix()} MeterValues measurand ${
460 meterValue.sampledValue[sampledValuesIndex].measurand ??
461 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
462 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
463 meterValue.sampledValue[sampledValuesIndex].value
464 }/${maxPowerRounded}`
465 );
466 }
467 for (
468 let phase = 1;
469 this.chargingStation.getNumberOfPhases() === 3 &&
470 phase <= this.chargingStation.getNumberOfPhases();
471 phase++
472 ) {
473 const phaseValue = `L${phase}-N`;
474 meterValue.sampledValue.push(
475 OCPP16ServiceUtils.buildSampledValue(
476 (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ??
477 powerSampledValueTemplate,
478 powerMeasurandValues[`L${phase}`] as number,
479 null,
480 phaseValue as OCPP16MeterValuePhase
481 )
482 );
483 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
484 const maxPowerPerPhaseRounded = Utils.roundTo(maxPowerPerPhase / unitDivider, 2);
485 if (
486 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
487 maxPowerPerPhaseRounded ||
488 debug
489 ) {
490 logger.error(
491 `${this.chargingStation.logPrefix()} MeterValues measurand ${
492 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
493 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
494 }: phase ${
495 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
496 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
497 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
498 }/${maxPowerPerPhaseRounded}`
499 );
500 }
501 }
502 }
503 // Current.Import measurand
504 const currentSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
505 connectorId,
506 OCPP16MeterValueMeasurand.CURRENT_IMPORT
507 );
508 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
509 if (this.chargingStation.getNumberOfPhases() === 3) {
510 currentPerPhaseSampledValueTemplates = {
511 L1: this.chargingStation.getSampledValueTemplate(
512 connectorId,
513 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
514 OCPP16MeterValuePhase.L1
515 ),
516 L2: this.chargingStation.getSampledValueTemplate(
517 connectorId,
518 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
519 OCPP16MeterValuePhase.L2
520 ),
521 L3: this.chargingStation.getSampledValueTemplate(
522 connectorId,
523 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
524 OCPP16MeterValuePhase.L3
525 ),
526 };
527 }
528 if (currentSampledValueTemplate) {
529 OCPP16ServiceUtils.checkMeasurandPowerDivider(
530 this.chargingStation,
531 currentSampledValueTemplate.measurand
532 );
533 const errMsg = `${this.chargingStation.logPrefix()} MeterValues measurand ${
534 currentSampledValueTemplate.measurand ??
535 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
536 }: Unknown ${this.chargingStation.getCurrentOutType()} currentOutType in template file ${
537 this.chargingStation.stationTemplateFile
538 }, cannot calculate ${
539 currentSampledValueTemplate.measurand ??
540 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
541 } measurand value`;
542 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
543 let maxAmperage: number;
544 switch (this.chargingStation.getCurrentOutType()) {
545 case CurrentType.AC:
546 maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(
547 this.chargingStation.getNumberOfPhases(),
548 this.chargingStation.stationInfo.maxPower /
549 this.chargingStation.stationInfo.powerDivider,
550 this.chargingStation.getVoltageOut()
551 );
552 if (this.chargingStation.getNumberOfPhases() === 3) {
553 const defaultFluctuatedAmperagePerPhase =
554 currentSampledValueTemplate.value &&
555 Utils.getRandomFloatFluctuatedRounded(
556 parseInt(currentSampledValueTemplate.value),
557 currentSampledValueTemplate.fluctuationPercent ??
558 Constants.DEFAULT_FLUCTUATION_PERCENT
559 );
560 const phase1FluctuatedValue =
561 currentPerPhaseSampledValueTemplates?.L1?.value &&
562 Utils.getRandomFloatFluctuatedRounded(
563 parseInt(currentPerPhaseSampledValueTemplates.L1.value),
564 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
565 Constants.DEFAULT_FLUCTUATION_PERCENT
566 );
567 const phase2FluctuatedValue =
568 currentPerPhaseSampledValueTemplates?.L2?.value &&
569 Utils.getRandomFloatFluctuatedRounded(
570 parseInt(currentPerPhaseSampledValueTemplates.L2.value),
571 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
572 Constants.DEFAULT_FLUCTUATION_PERCENT
573 );
574 const phase3FluctuatedValue =
575 currentPerPhaseSampledValueTemplates?.L3?.value &&
576 Utils.getRandomFloatFluctuatedRounded(
577 parseInt(currentPerPhaseSampledValueTemplates.L3.value),
578 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
579 Constants.DEFAULT_FLUCTUATION_PERCENT
580 );
581 currentMeasurandValues.L1 =
582 phase1FluctuatedValue ??
583 defaultFluctuatedAmperagePerPhase ??
584 Utils.getRandomFloatRounded(maxAmperage);
585 currentMeasurandValues.L2 =
586 phase2FluctuatedValue ??
587 defaultFluctuatedAmperagePerPhase ??
588 Utils.getRandomFloatRounded(maxAmperage);
589 currentMeasurandValues.L3 =
590 phase3FluctuatedValue ??
591 defaultFluctuatedAmperagePerPhase ??
592 Utils.getRandomFloatRounded(maxAmperage);
593 } else {
594 currentMeasurandValues.L1 = currentSampledValueTemplate.value
595 ? Utils.getRandomFloatFluctuatedRounded(
596 parseInt(currentSampledValueTemplate.value),
597 currentSampledValueTemplate.fluctuationPercent ??
598 Constants.DEFAULT_FLUCTUATION_PERCENT
599 )
600 : Utils.getRandomFloatRounded(maxAmperage);
601 currentMeasurandValues.L2 = 0;
602 currentMeasurandValues.L3 = 0;
603 }
604 currentMeasurandValues.allPhases = Utils.roundTo(
605 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
606 this.chargingStation.getNumberOfPhases(),
607 2
608 );
609 break;
610 case CurrentType.DC:
611 maxAmperage = DCElectricUtils.amperage(
612 this.chargingStation.stationInfo.maxPower /
613 this.chargingStation.stationInfo.powerDivider,
614 this.chargingStation.getVoltageOut()
615 );
616 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
617 ? Utils.getRandomFloatFluctuatedRounded(
618 parseInt(currentSampledValueTemplate.value),
619 currentSampledValueTemplate.fluctuationPercent ??
620 Constants.DEFAULT_FLUCTUATION_PERCENT
621 )
622 : Utils.getRandomFloatRounded(maxAmperage);
623 break;
624 default:
625 logger.error(errMsg);
626 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
627 }
628 meterValue.sampledValue.push(
629 OCPP16ServiceUtils.buildSampledValue(
630 currentSampledValueTemplate,
631 currentMeasurandValues.allPhases
632 )
633 );
634 const sampledValuesIndex = meterValue.sampledValue.length - 1;
635 if (
636 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage ||
637 debug
638 ) {
639 logger.error(
640 `${this.chargingStation.logPrefix()} MeterValues measurand ${
641 meterValue.sampledValue[sampledValuesIndex].measurand ??
642 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
643 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
644 meterValue.sampledValue[sampledValuesIndex].value
645 }/${maxAmperage}`
646 );
647 }
648 for (
649 let phase = 1;
650 this.chargingStation.getNumberOfPhases() === 3 &&
651 phase <= this.chargingStation.getNumberOfPhases();
652 phase++
653 ) {
654 const phaseValue = `L${phase}`;
655 meterValue.sampledValue.push(
656 OCPP16ServiceUtils.buildSampledValue(
657 (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
658 currentSampledValueTemplate,
659 currentMeasurandValues[phaseValue] as number,
660 null,
661 phaseValue as OCPP16MeterValuePhase
662 )
663 );
664 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
665 if (
666 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
667 maxAmperage ||
668 debug
669 ) {
670 logger.error(
671 `${this.chargingStation.logPrefix()} MeterValues measurand ${
672 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
673 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
674 }: phase ${
675 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
676 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
677 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
678 }/${maxAmperage}`
679 );
680 }
681 }
682 }
683 // Energy.Active.Import.Register measurand (default)
684 const energySampledValueTemplate = this.chargingStation.getSampledValueTemplate(connectorId);
685 if (energySampledValueTemplate) {
686 OCPP16ServiceUtils.checkMeasurandPowerDivider(
687 this.chargingStation,
688 energySampledValueTemplate.measurand
689 );
690 const unitDivider =
691 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
692 const maxEnergyRounded = Utils.roundTo(
693 ((this.chargingStation.stationInfo.maxPower /
694 this.chargingStation.stationInfo.powerDivider) *
695 interval) /
696 (3600 * 1000),
697 2
698 );
699 const energyValueRounded = energySampledValueTemplate.value
700 ? // Cumulate the fluctuated value around the static one
701 Utils.getRandomFloatFluctuatedRounded(
702 parseInt(energySampledValueTemplate.value),
703 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
704 )
705 : Utils.getRandomFloatRounded(maxEnergyRounded);
706 // Persist previous value on connector
707 if (
708 connector &&
709 !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) &&
710 connector.energyActiveImportRegisterValue >= 0 &&
711 !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) &&
712 connector.transactionEnergyActiveImportRegisterValue >= 0
713 ) {
714 connector.energyActiveImportRegisterValue += energyValueRounded;
715 connector.transactionEnergyActiveImportRegisterValue += energyValueRounded;
716 } else {
717 connector.energyActiveImportRegisterValue = 0;
718 connector.transactionEnergyActiveImportRegisterValue = 0;
719 }
720 meterValue.sampledValue.push(
721 OCPP16ServiceUtils.buildSampledValue(
722 energySampledValueTemplate,
723 Utils.roundTo(
724 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
725 unitDivider,
726 2
727 )
728 )
729 );
730 const sampledValuesIndex = meterValue.sampledValue.length - 1;
731 if (energyValueRounded > maxEnergyRounded || debug) {
732 logger.error(
733 `${this.chargingStation.logPrefix()} MeterValues measurand ${
734 meterValue.sampledValue[sampledValuesIndex].measurand ??
735 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
736 }: connectorId ${connectorId}, transaction ${
737 connector.transactionId
738 }, value: ${energyValueRounded}/${maxEnergyRounded}, duration: ${Utils.roundTo(
739 interval / (3600 * 1000),
740 4
741 )}h`
742 );
743 }
744 }
745 const payload: MeterValuesRequest = {
746 connectorId,
747 transactionId,
748 meterValue: [meterValue],
749 };
750 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.METER_VALUES);
751 }
752
753 public async sendTransactionBeginMeterValues(
754 connectorId: number,
755 transactionId: number,
756 beginMeterValue: OCPP16MeterValue
757 ): Promise<void> {
758 const payload: MeterValuesRequest = {
759 connectorId,
760 transactionId,
761 meterValue: [beginMeterValue],
762 };
763 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.METER_VALUES);
764 }
765
766 public async sendTransactionEndMeterValues(
767 connectorId: number,
768 transactionId: number,
769 endMeterValue: OCPP16MeterValue
770 ): Promise<void> {
771 const payload: MeterValuesRequest = {
772 connectorId,
773 transactionId,
774 meterValue: [endMeterValue],
775 };
776 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.METER_VALUES);
777 }
778
779 public async sendDiagnosticsStatusNotification(
780 diagnosticsStatus: OCPP16DiagnosticsStatus
781 ): Promise<void> {
782 const payload: DiagnosticsStatusNotificationRequest = {
783 status: diagnosticsStatus,
784 };
785 await this.sendMessage(
786 Utils.generateUUID(),
787 payload,
788 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
789 );
790 }
791 }