Remove useless eslint-disable
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16RequestService.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
4a1857a2 3import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
e7aeea18
JB
4import {
5 AuthorizeRequest,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionReason,
9 OCPP16StopTransactionResponse,
10 StartTransactionRequest,
11 StopTransactionRequest,
12} from '../../../types/ocpp/1.6/Transaction';
4c2b4904 13import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
e7aeea18
JB
14import {
15 DiagnosticsStatusNotificationRequest,
16 HeartbeatRequest,
17 OCPP16BootNotificationRequest,
18 OCPP16RequestCommand,
19 StatusNotificationRequest,
20} from '../../../types/ocpp/1.6/Requests';
c0f4be74
JB
21import MeasurandPerPhaseSampledValueTemplates, {
22 SampledValueTemplate,
23} from '../../../types/MeasurandPerPhaseSampledValueTemplates';
e7aeea18
JB
24import {
25 MeterValueUnit,
26 MeterValuesRequest,
27 OCPP16MeterValue,
28 OCPP16MeterValueMeasurand,
29 OCPP16MeterValuePhase,
30} from '../../../types/ocpp/1.6/MeterValues';
c0560973 31
73b9adec 32import type ChargingStation from '../../ChargingStation';
c0560973 33import Constants from '../../../utils/Constants';
14763b46 34import { ErrorType } from '../../../types/ocpp/ErrorType';
c0560973 35import MeasurandValues from '../../../types/MeasurandValues';
efa43e52 36import { OCPP16BootNotificationResponse } from '../../../types/ocpp/1.6/Responses';
c0560973
JB
37import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
38import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
47e22477 39import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
6ed92bc1 40import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
e58068fd 41import OCPPError from '../../../exception/OCPPError';
c0560973 42import OCPPRequestService from '../OCPPRequestService';
73b9adec 43import type OCPPResponseService from '../OCPPResponseService';
caad9d6b 44import { SendParams } from '../../../types/ocpp/Requests';
c0560973 45import Utils from '../../../utils/Utils';
9f2e3130 46import logger from '../../../utils/Logger';
c0560973 47
909dcf2d
JB
48const moduleName = 'OCPP16RequestService';
49
c0560973 50export default class OCPP16RequestService extends OCPPRequestService {
9f2e3130 51 public constructor(chargingStation: ChargingStation, ocppResponseService: OCPPResponseService) {
909dcf2d 52 if (new.target?.name === moduleName) {
06127450 53 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130
JB
54 }
55 super(chargingStation, ocppResponseService);
56 }
57
caad9d6b 58 public async sendHeartbeat(params?: SendParams): Promise<void> {
5e0c67e8
JB
59 const payload: HeartbeatRequest = {};
60 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.HEARTBEAT, params);
c0560973
JB
61 }
62
e7aeea18
JB
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> {
5e0c67e8
JB
75 const payload: OCPP16BootNotificationRequest = {
76 chargePointModel,
77 chargePointVendor,
e7aeea18
JB
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 }),
5e0c67e8 85 };
e7aeea18
JB
86 return (await this.sendMessage(
87 Utils.generateUUID(),
88 payload,
89 OCPP16RequestCommand.BOOT_NOTIFICATION,
90 { ...params, skipBufferingOnError: true }
91 )) as OCPP16BootNotificationResponse;
c0560973
JB
92 }
93
e7aeea18
JB
94 public async sendStatusNotification(
95 connectorId: number,
96 status: OCPP16ChargePointStatus,
97 errorCode: OCPP16ChargePointErrorCode = OCPP16ChargePointErrorCode.NO_ERROR
98 ): Promise<void> {
5e0c67e8
JB
99 const payload: StatusNotificationRequest = {
100 connectorId,
101 errorCode,
102 status,
103 };
104 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.STATUS_NOTIFICATION);
c0560973
JB
105 }
106
e7aeea18
JB
107 public async sendAuthorize(
108 connectorId: number,
109 idTag?: string
110 ): Promise<OCPP16AuthorizeResponse> {
5e0c67e8 111 const payload: AuthorizeRequest = {
e7aeea18 112 ...(!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.DEFAULT_IDTAG }),
5e0c67e8
JB
113 };
114 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag = idTag;
e7aeea18
JB
115 return (await this.sendMessage(
116 Utils.generateUUID(),
117 payload,
118 OCPP16RequestCommand.AUTHORIZE
119 )) as OCPP16AuthorizeResponse;
c0560973
JB
120 }
121
e7aeea18
JB
122 public async sendStartTransaction(
123 connectorId: number,
124 idTag?: string
125 ): Promise<OCPP16StartTransactionResponse> {
5e0c67e8
JB
126 const payload: StartTransactionRequest = {
127 connectorId,
e7aeea18 128 ...(!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.DEFAULT_IDTAG }),
5e0c67e8
JB
129 meterStart: this.chargingStation.getEnergyActiveImportRegisterByConnectorId(connectorId),
130 timestamp: new Date().toISOString(),
131 };
e7aeea18
JB
132 return (await this.sendMessage(
133 Utils.generateUUID(),
134 payload,
135 OCPP16RequestCommand.START_TRANSACTION
136 )) as OCPP16StartTransactionResponse;
c0560973
JB
137 }
138
e7aeea18
JB
139 public async sendStopTransaction(
140 transactionId: number,
141 meterStop: number,
142 idTag?: string,
143 reason: OCPP16StopTransactionReason = OCPP16StopTransactionReason.NONE
144 ): Promise<OCPP16StopTransactionResponse> {
5e0c67e8
JB
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;
326f6e38 150 }
c0560973 151 }
e7aeea18
JB
152 const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
153 this.chargingStation,
154 connectorId,
155 meterStop
156 );
5e0c67e8 157 // FIXME: should be a callback, each OCPP commands implementation must do only one job
e7aeea18
JB
158 this.chargingStation.getBeginEndMeterValues() &&
159 this.chargingStation.getOcppStrictCompliance() &&
160 !this.chargingStation.getOutOfOrderEndMeterValues() &&
161 (await this.sendTransactionEndMeterValues(
162 connectorId,
163 transactionId,
164 transactionEndMeterValue
165 ));
5e0c67e8
JB
166 const payload: StopTransactionRequest = {
167 transactionId,
e7aeea18 168 ...(!Utils.isUndefined(idTag) && { idTag }),
5e0c67e8
JB
169 meterStop,
170 timestamp: new Date().toISOString(),
e7aeea18
JB
171 ...(reason && { reason }),
172 ...(this.chargingStation.getTransactionDataMeterValues() && {
173 transactionData: OCPP16ServiceUtils.buildTransactionDataMeterValues(
174 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue,
175 transactionEndMeterValue
176 ),
177 }),
5e0c67e8 178 };
e7aeea18
JB
179 return (await this.sendMessage(
180 Utils.generateUUID(),
181 payload,
182 OCPP16RequestCommand.STOP_TRANSACTION
183 )) as OCPP16StartTransactionResponse;
c0560973
JB
184 }
185
e7aeea18
JB
186 public async sendMeterValues(
187 connectorId: number,
188 transactionId: number,
189 interval: number,
190 debug = false
191 ): Promise<void> {
5e0c67e8
JB
192 const meterValue: OCPP16MeterValue = {
193 timestamp: new Date().toISOString(),
194 sampledValue: [],
195 };
196 const connector = this.chargingStation.getConnectorStatus(connectorId);
197 // SoC measurand
e7aeea18
JB
198 const socSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
199 connectorId,
200 OCPP16MeterValueMeasurand.STATE_OF_CHARGE
201 );
5e0c67e8
JB
202 if (socSampledValueTemplate) {
203 const socSampledValueTemplateValue = socSampledValueTemplate.value
e7aeea18
JB
204 ? Utils.getRandomFloatFluctuatedRounded(
205 parseInt(socSampledValueTemplate.value),
206 socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
207 )
5e0c67e8 208 : Utils.getRandomInteger(100);
e7aeea18
JB
209 meterValue.sampledValue.push(
210 OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
211 );
5e0c67e8
JB
212 const sampledValuesIndex = meterValue.sampledValue.length - 1;
213 if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
e7aeea18
JB
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 );
5e0c67e8
JB
222 }
223 }
224 // Voltage measurand
e7aeea18
JB
225 const voltageSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
226 connectorId,
227 OCPP16MeterValueMeasurand.VOLTAGE
228 );
5e0c67e8 229 if (voltageSampledValueTemplate) {
e7aeea18
JB
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 );
9ccca265 247 }
e7aeea18
JB
248 for (
249 let phase = 1;
250 this.chargingStation.getNumberOfPhases() === 3 &&
251 phase <= this.chargingStation.getNumberOfPhases();
252 phase++
253 ) {
5e0c67e8 254 const phaseLineToNeutralValue = `L${phase}-N`;
e7aeea18
JB
255 const voltagePhaseLineToNeutralSampledValueTemplate =
256 this.chargingStation.getSampledValueTemplate(
257 connectorId,
258 OCPP16MeterValueMeasurand.VOLTAGE,
259 phaseLineToNeutralValue as OCPP16MeterValuePhase
260 );
5e0c67e8
JB
261 let voltagePhaseLineToNeutralMeasurandValue: number;
262 if (voltagePhaseLineToNeutralSampledValueTemplate) {
e7aeea18
JB
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 );
9ccca265 274 }
e7aeea18
JB
275 meterValue.sampledValue.push(
276 OCPP16ServiceUtils.buildSampledValue(
277 voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
278 voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
279 null,
280 phaseLineToNeutralValue as OCPP16MeterValuePhase
281 )
282 );
5e0c67e8 283 if (this.chargingStation.getPhaseLineToLineVoltageMeterValues()) {
e7aeea18
JB
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 );
5e0c67e8
JB
295 let voltagePhaseLineToLineMeasurandValue: number;
296 if (voltagePhaseLineToLineSampledValueTemplate) {
e7aeea18
JB
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 );
c0560973 308 }
e7aeea18
JB
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 );
9ccca265
JB
321 }
322 }
5e0c67e8
JB
323 }
324 // Power.Active.Import measurand
e7aeea18
JB
325 const powerSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
326 connectorId,
327 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
328 );
5e0c67e8
JB
329 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
330 if (this.chargingStation.getNumberOfPhases() === 3) {
331 powerPerPhaseSampledValueTemplates = {
e7aeea18
JB
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 ),
5e0c67e8
JB
347 };
348 }
349 if (powerSampledValueTemplate) {
e7aeea18
JB
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`;
5e0c67e8
JB
363 const powerMeasurandValues = {} as MeasurandValues;
364 const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
e7aeea18
JB
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 );
5e0c67e8
JB
373 switch (this.chargingStation.getCurrentOutType()) {
374 case CurrentType.AC:
375 if (this.chargingStation.getNumberOfPhases() === 3) {
e7aeea18
JB
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);
5e0c67e8
JB
417 } else {
418 powerMeasurandValues.L1 = powerSampledValueTemplate.value
e7aeea18
JB
419 ? Utils.getRandomFloatFluctuatedRounded(
420 parseInt(powerSampledValueTemplate.value),
421 powerSampledValueTemplate.fluctuationPercent ??
422 Constants.DEFAULT_FLUCTUATION_PERCENT
423 )
9ccca265 424 : Utils.getRandomFloatRounded(maxPower / unitDivider);
5e0c67e8
JB
425 powerMeasurandValues.L2 = 0;
426 powerMeasurandValues.L3 = 0;
c0560973 427 }
e7aeea18
JB
428 powerMeasurandValues.allPhases = Utils.roundTo(
429 powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
430 2
431 );
5e0c67e8
JB
432 break;
433 case CurrentType.DC:
434 powerMeasurandValues.allPhases = powerSampledValueTemplate.value
e7aeea18
JB
435 ? Utils.getRandomFloatFluctuatedRounded(
436 parseInt(powerSampledValueTemplate.value),
437 powerSampledValueTemplate.fluctuationPercent ??
438 Constants.DEFAULT_FLUCTUATION_PERCENT
439 )
5e0c67e8
JB
440 : Utils.getRandomFloatRounded(maxPower / unitDivider);
441 break;
442 default:
443 logger.error(errMsg);
444 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
9ccca265 445 }
e7aeea18
JB
446 meterValue.sampledValue.push(
447 OCPP16ServiceUtils.buildSampledValue(
448 powerSampledValueTemplate,
449 powerMeasurandValues.allPhases
450 )
451 );
5e0c67e8
JB
452 const sampledValuesIndex = meterValue.sampledValue.length - 1;
453 const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2);
e7aeea18
JB
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 );
9ccca265 466 }
e7aeea18
JB
467 for (
468 let phase = 1;
469 this.chargingStation.getNumberOfPhases() === 3 &&
470 phase <= this.chargingStation.getNumberOfPhases();
471 phase++
472 ) {
5e0c67e8 473 const phaseValue = `L${phase}-N`;
e7aeea18
JB
474 meterValue.sampledValue.push(
475 OCPP16ServiceUtils.buildSampledValue(
c0f4be74
JB
476 (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ??
477 powerSampledValueTemplate,
478 powerMeasurandValues[`L${phase}`] as number,
e7aeea18
JB
479 null,
480 phaseValue as OCPP16MeterValuePhase
481 )
482 );
5e0c67e8
JB
483 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
484 const maxPowerPerPhaseRounded = Utils.roundTo(maxPowerPerPhase / unitDivider, 2);
e7aeea18
JB
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 );
5e0c67e8
JB
500 }
501 }
502 }
503 // Current.Import measurand
e7aeea18
JB
504 const currentSampledValueTemplate = this.chargingStation.getSampledValueTemplate(
505 connectorId,
506 OCPP16MeterValueMeasurand.CURRENT_IMPORT
507 );
5e0c67e8
JB
508 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
509 if (this.chargingStation.getNumberOfPhases() === 3) {
510 currentPerPhaseSampledValueTemplates = {
e7aeea18
JB
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 ),
5e0c67e8
JB
526 };
527 }
528 if (currentSampledValueTemplate) {
e7aeea18
JB
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`;
5e0c67e8
JB
542 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
543 let maxAmperage: number;
544 switch (this.chargingStation.getCurrentOutType()) {
545 case CurrentType.AC:
e7aeea18
JB
546 maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(
547 this.chargingStation.getNumberOfPhases(),
548 this.chargingStation.stationInfo.maxPower /
549 this.chargingStation.stationInfo.powerDivider,
550 this.chargingStation.getVoltageOut()
551 );
5e0c67e8 552 if (this.chargingStation.getNumberOfPhases() === 3) {
e7aeea18
JB
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);
5e0c67e8
JB
593 } else {
594 currentMeasurandValues.L1 = currentSampledValueTemplate.value
e7aeea18
JB
595 ? Utils.getRandomFloatFluctuatedRounded(
596 parseInt(currentSampledValueTemplate.value),
597 currentSampledValueTemplate.fluctuationPercent ??
598 Constants.DEFAULT_FLUCTUATION_PERCENT
599 )
9ccca265 600 : Utils.getRandomFloatRounded(maxAmperage);
5e0c67e8
JB
601 currentMeasurandValues.L2 = 0;
602 currentMeasurandValues.L3 = 0;
c0560973 603 }
e7aeea18
JB
604 currentMeasurandValues.allPhases = Utils.roundTo(
605 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
606 this.chargingStation.getNumberOfPhases(),
607 2
608 );
5e0c67e8
JB
609 break;
610 case CurrentType.DC:
e7aeea18
JB
611 maxAmperage = DCElectricUtils.amperage(
612 this.chargingStation.stationInfo.maxPower /
613 this.chargingStation.stationInfo.powerDivider,
614 this.chargingStation.getVoltageOut()
615 );
5e0c67e8 616 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
e7aeea18
JB
617 ? Utils.getRandomFloatFluctuatedRounded(
618 parseInt(currentSampledValueTemplate.value),
619 currentSampledValueTemplate.fluctuationPercent ??
620 Constants.DEFAULT_FLUCTUATION_PERCENT
621 )
5e0c67e8
JB
622 : Utils.getRandomFloatRounded(maxAmperage);
623 break;
624 default:
625 logger.error(errMsg);
626 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
627 }
e7aeea18
JB
628 meterValue.sampledValue.push(
629 OCPP16ServiceUtils.buildSampledValue(
630 currentSampledValueTemplate,
631 currentMeasurandValues.allPhases
632 )
633 );
5e0c67e8 634 const sampledValuesIndex = meterValue.sampledValue.length - 1;
e7aeea18
JB
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 );
5e0c67e8 647 }
e7aeea18
JB
648 for (
649 let phase = 1;
650 this.chargingStation.getNumberOfPhases() === 3 &&
651 phase <= this.chargingStation.getNumberOfPhases();
652 phase++
653 ) {
5e0c67e8 654 const phaseValue = `L${phase}`;
e7aeea18
JB
655 meterValue.sampledValue.push(
656 OCPP16ServiceUtils.buildSampledValue(
c0f4be74
JB
657 (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
658 currentSampledValueTemplate,
659 currentMeasurandValues[phaseValue] as number,
e7aeea18
JB
660 null,
661 phaseValue as OCPP16MeterValuePhase
662 )
663 );
5e0c67e8 664 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
e7aeea18
JB
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 );
9ccca265
JB
680 }
681 }
5e0c67e8
JB
682 }
683 // Energy.Active.Import.Register measurand (default)
684 const energySampledValueTemplate = this.chargingStation.getSampledValueTemplate(connectorId);
685 if (energySampledValueTemplate) {
e7aeea18
JB
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 );
5e0c67e8 699 const energyValueRounded = energySampledValueTemplate.value
e7aeea18
JB
700 ? // Cumulate the fluctuated value around the static one
701 Utils.getRandomFloatFluctuatedRounded(
702 parseInt(energySampledValueTemplate.value),
703 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
704 )
5e0c67e8 705 : Utils.getRandomFloatRounded(maxEnergyRounded);
e7aeea18
JB
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 ) {
5e0c67e8
JB
714 connector.energyActiveImportRegisterValue += energyValueRounded;
715 connector.transactionEnergyActiveImportRegisterValue += energyValueRounded;
716 } else {
717 connector.energyActiveImportRegisterValue = 0;
718 connector.transactionEnergyActiveImportRegisterValue = 0;
719 }
e7aeea18
JB
720 meterValue.sampledValue.push(
721 OCPP16ServiceUtils.buildSampledValue(
722 energySampledValueTemplate,
723 Utils.roundTo(
724 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
725 unitDivider,
726 2
727 )
728 )
729 );
5e0c67e8
JB
730 const sampledValuesIndex = meterValue.sampledValue.length - 1;
731 if (energyValueRounded > maxEnergyRounded || debug) {
e7aeea18
JB
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 );
c0560973 743 }
c0560973 744 }
5e0c67e8
JB
745 const payload: MeterValuesRequest = {
746 connectorId,
747 transactionId,
748 meterValue: [meterValue],
749 };
750 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.METER_VALUES);
c0560973
JB
751 }
752
e7aeea18
JB
753 public async sendTransactionBeginMeterValues(
754 connectorId: number,
755 transactionId: number,
756 beginMeterValue: OCPP16MeterValue
757 ): Promise<void> {
5e0c67e8
JB
758 const payload: MeterValuesRequest = {
759 connectorId,
760 transactionId,
761 meterValue: [beginMeterValue],
762 };
763 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.METER_VALUES);
326f6e38
JB
764 }
765
e7aeea18
JB
766 public async sendTransactionEndMeterValues(
767 connectorId: number,
768 transactionId: number,
769 endMeterValue: OCPP16MeterValue
770 ): Promise<void> {
5e0c67e8
JB
771 const payload: MeterValuesRequest = {
772 connectorId,
773 transactionId,
774 meterValue: [endMeterValue],
775 };
776 await this.sendMessage(Utils.generateUUID(), payload, OCPP16RequestCommand.METER_VALUES);
326f6e38 777 }
6ed92bc1 778
e7aeea18
JB
779 public async sendDiagnosticsStatusNotification(
780 diagnosticsStatus: OCPP16DiagnosticsStatus
781 ): Promise<void> {
5e0c67e8 782 const payload: DiagnosticsStatusNotificationRequest = {
e7aeea18 783 status: diagnosticsStatus,
5e0c67e8 784 };
e7aeea18
JB
785 await this.sendMessage(
786 Utils.generateUUID(),
787 payload,
788 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
789 );
6ed92bc1 790 }
c0560973 791}