Commit | Line | Data |
---|---|---|
c8eeb62b JB |
1 | // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. |
2 | ||
4a1857a2 | 3 | import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils'; |
e7aeea18 JB |
4 | import { |
5 | AuthorizeRequest, | |
6 | OCPP16AuthorizeResponse, | |
7 | OCPP16StartTransactionResponse, | |
8 | OCPP16StopTransactionReason, | |
9 | OCPP16StopTransactionResponse, | |
10 | StartTransactionRequest, | |
11 | StopTransactionRequest, | |
12 | } from '../../../types/ocpp/1.6/Transaction'; | |
4c2b4904 | 13 | import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate'; |
e7aeea18 JB |
14 | import { |
15 | DiagnosticsStatusNotificationRequest, | |
16 | HeartbeatRequest, | |
17 | OCPP16BootNotificationRequest, | |
18 | OCPP16RequestCommand, | |
19 | StatusNotificationRequest, | |
20 | } from '../../../types/ocpp/1.6/Requests'; | |
c0f4be74 JB |
21 | import MeasurandPerPhaseSampledValueTemplates, { |
22 | SampledValueTemplate, | |
23 | } from '../../../types/MeasurandPerPhaseSampledValueTemplates'; | |
e7aeea18 JB |
24 | import { |
25 | MeterValueUnit, | |
26 | MeterValuesRequest, | |
27 | OCPP16MeterValue, | |
28 | OCPP16MeterValueMeasurand, | |
29 | OCPP16MeterValuePhase, | |
30 | } from '../../../types/ocpp/1.6/MeterValues'; | |
c0560973 | 31 | |
73b9adec | 32 | import type ChargingStation from '../../ChargingStation'; |
c0560973 | 33 | import Constants from '../../../utils/Constants'; |
14763b46 | 34 | import { ErrorType } from '../../../types/ocpp/ErrorType'; |
c0560973 | 35 | import MeasurandValues from '../../../types/MeasurandValues'; |
efa43e52 | 36 | import { OCPP16BootNotificationResponse } from '../../../types/ocpp/1.6/Responses'; |
c0560973 JB |
37 | import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode'; |
38 | import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus'; | |
47e22477 | 39 | import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus'; |
6ed92bc1 | 40 | import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; |
e58068fd | 41 | import OCPPError from '../../../exception/OCPPError'; |
c0560973 | 42 | import OCPPRequestService from '../OCPPRequestService'; |
73b9adec | 43 | import type OCPPResponseService from '../OCPPResponseService'; |
caad9d6b | 44 | import { SendParams } from '../../../types/ocpp/Requests'; |
c0560973 | 45 | import Utils from '../../../utils/Utils'; |
9f2e3130 | 46 | import logger from '../../../utils/Logger'; |
c0560973 | 47 | |
909dcf2d JB |
48 | const moduleName = 'OCPP16RequestService'; |
49 | ||
c0560973 | 50 | export 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 | } |