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