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