1 import { readFileSync
} from
'node:fs'
2 import { dirname
, join
} from
'node:path'
3 import { fileURLToPath
} from
'node:url'
5 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv'
6 import { isDate
} from
'date-fns'
8 import { OCPP16Constants
} from
'./1.6/OCPP16Constants.js'
9 import { OCPP20Constants
} from
'./2.0/OCPP20Constants.js'
10 import { OCPPConstants
} from
'./OCPPConstants.js'
15 } from
'../../charging-station/index.js'
16 import { BaseError
, OCPPError
} from
'../../exception/index.js'
19 type AuthorizeRequest
,
20 type AuthorizeResponse
,
22 ChargingStationEvents
,
24 type ConnectorStatusEnum
,
28 IncomingRequestCommand
,
30 type MeasurandPerPhaseSampledValueTemplates
,
40 type OCPP16StatusNotificationRequest
,
41 type OCPP20StatusNotificationRequest
,
45 type SampledValueTemplate
,
46 StandardParametersKey
,
47 type StatusNotificationRequest
,
48 type StatusNotificationResponse
49 } from
'../../types/index.js'
56 getRandomFloatFluctuatedRounded
,
57 getRandomFloatRounded
,
69 } from
'../../utils/index.js'
71 export const getMessageTypeString
= (messageType
: MessageType
): string => {
72 switch (messageType
) {
73 case MessageType
.CALL_MESSAGE
:
75 case MessageType
.CALL_RESULT_MESSAGE
:
77 case MessageType
.CALL_ERROR_MESSAGE
:
84 export const buildStatusNotificationRequest
= (
85 chargingStation
: ChargingStation
,
87 status: ConnectorStatusEnum
,
89 ): StatusNotificationRequest
=> {
90 switch (chargingStation
.stationInfo
?.ocppVersion
) {
91 case OCPPVersion
.VERSION_16
:
95 errorCode
: ChargePointErrorCode
.NO_ERROR
96 } satisfies OCPP16StatusNotificationRequest
97 case OCPPVersion
.VERSION_20
:
98 case OCPPVersion
.VERSION_201
:
100 timestamp
: new Date(),
101 connectorStatus
: status,
104 } satisfies OCPP20StatusNotificationRequest
106 throw new BaseError('Cannot build status notification payload: OCPP version not supported')
110 export const isIdTagAuthorized
= async (
111 chargingStation
: ChargingStation
,
114 ): Promise
<boolean> => {
116 !chargingStation
.getLocalAuthListEnabled() &&
117 chargingStation
.stationInfo
?.remoteAuthorization
=== false
120 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
123 if (chargingStation
.getLocalAuthListEnabled() && isIdTagLocalAuthorized(chargingStation
, idTag
)) {
124 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
125 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
126 connectorStatus
.localAuthorizeIdTag
= idTag
127 connectorStatus
.idTagLocalAuthorized
= true
129 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
130 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
135 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
137 chargingStation
.hasIdTags() &&
139 chargingStation
.idTagsCache
140 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
141 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
142 ?.find((tag
) => tag
=== idTag
)
147 const isIdTagRemoteAuthorized
= async (
148 chargingStation
: ChargingStation
,
151 ): Promise
<boolean> => {
152 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
153 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
156 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
158 RequestCommand
.AUTHORIZE
,
163 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
167 export const sendAndSetConnectorStatus
= async (
168 chargingStation
: ChargingStation
,
170 status: ConnectorStatusEnum
,
172 options
?: { send
: boolean }
173 ): Promise
<void> => {
174 options
= { send
: true, ...options
}
176 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
177 await chargingStation
.ocppRequestService
.requestHandler
<
178 StatusNotificationRequest
,
179 StatusNotificationResponse
182 RequestCommand
.STATUS_NOTIFICATION
,
183 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
186 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
187 chargingStation
.getConnectorStatus(connectorId
)!.status = status
188 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
190 ...chargingStation
.getConnectorStatus(connectorId
)
194 const checkConnectorStatusTransition
= (
195 chargingStation
: ChargingStation
,
197 status: ConnectorStatusEnum
199 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
200 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status
201 let transitionAllowed
= false
202 switch (chargingStation
.stationInfo
?.ocppVersion
) {
203 case OCPPVersion
.VERSION_16
:
205 (connectorId
=== 0 &&
206 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
207 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
210 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
211 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
214 transitionAllowed
= true
217 case OCPPVersion
.VERSION_20
:
218 case OCPPVersion
.VERSION_201
:
220 (connectorId
=== 0 &&
221 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
222 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
225 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
226 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
229 transitionAllowed
= true
234 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
237 if (!transitionAllowed
) {
239 `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
240 ?.ocppVersion} connector id ${connectorId} status transition from '${
241 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
242 chargingStation.getConnectorStatus(connectorId)!.status
243 }' to '${status}' is not allowed`
246 return transitionAllowed
249 export const buildMeterValue
= (
250 chargingStation
: ChargingStation
,
252 transactionId
: number,
256 const connector
= chargingStation
.getConnectorStatus(connectorId
)
257 let meterValue
: MeterValue
258 let socSampledValueTemplate
: SampledValueTemplate
| undefined
259 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
260 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
261 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
262 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
263 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
264 let energySampledValueTemplate
: SampledValueTemplate
| undefined
265 switch (chargingStation
.stationInfo
?.ocppVersion
) {
266 case OCPPVersion
.VERSION_16
:
268 timestamp
: new Date(),
272 socSampledValueTemplate
= getSampledValueTemplate(
275 MeterValueMeasurand
.STATE_OF_CHARGE
277 if (socSampledValueTemplate
!= null) {
278 const socMaximumValue
= 100
279 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
280 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
281 ? getRandomFloatFluctuatedRounded(
282 parseInt(socSampledValueTemplate
.value
),
283 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
285 : getRandomInteger(socMaximumValue
, socMinimumValue
)
286 meterValue
.sampledValue
.push(
287 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
289 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
291 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
292 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
296 `${chargingStation.logPrefix()} MeterValues measurand ${
297 meterValue.sampledValue[sampledValuesIndex].measurand ??
298 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
299 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
300 meterValue.sampledValue[sampledValuesIndex].value
301 }/${socMaximumValue}`
306 voltageSampledValueTemplate
= getSampledValueTemplate(
309 MeterValueMeasurand
.VOLTAGE
311 if (voltageSampledValueTemplate
!= null) {
312 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
313 ? parseInt(voltageSampledValueTemplate
.value
)
314 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
315 chargingStation
.stationInfo
.voltageOut
!
316 const fluctuationPercent
=
317 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
318 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
319 voltageSampledValueTemplateValue
,
323 chargingStation
.getNumberOfPhases() !== 3 ||
324 (chargingStation
.getNumberOfPhases() === 3 &&
325 chargingStation
.stationInfo
?.mainVoltageMeterValues
=== true)
327 meterValue
.sampledValue
.push(
328 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
333 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
336 const phaseLineToNeutralValue
= `L${phase}-N`
337 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
340 MeterValueMeasurand
.VOLTAGE
,
341 phaseLineToNeutralValue
as MeterValuePhase
343 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
344 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
345 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
346 voltagePhaseLineToNeutralSampledValueTemplate
.value
348 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
349 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
350 chargingStation
.stationInfo
.voltageOut
!
351 const fluctuationPhaseToNeutralPercent
=
352 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
353 Constants
.DEFAULT_FLUCTUATION_PERCENT
354 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
355 voltagePhaseLineToNeutralSampledValueTemplateValue
,
356 fluctuationPhaseToNeutralPercent
359 meterValue
.sampledValue
.push(
361 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
362 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
364 phaseLineToNeutralValue
as MeterValuePhase
367 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
=== true) {
368 const phaseLineToLineValue
= `L${phase}-L${
369 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
370 ? (phase + 1) % chargingStation.getNumberOfPhases()
371 : chargingStation.getNumberOfPhases()
373 const voltagePhaseLineToLineValueRounded
= roundTo(
374 Math.sqrt(chargingStation
.getNumberOfPhases()) *
375 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
376 chargingStation
.stationInfo
.voltageOut
!,
379 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
382 MeterValueMeasurand
.VOLTAGE
,
383 phaseLineToLineValue
as MeterValuePhase
385 let voltagePhaseLineToLineMeasurandValue
: number | undefined
386 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
387 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
388 voltagePhaseLineToLineSampledValueTemplate
.value
390 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
391 : voltagePhaseLineToLineValueRounded
392 const fluctuationPhaseLineToLinePercent
=
393 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
394 Constants
.DEFAULT_FLUCTUATION_PERCENT
395 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
396 voltagePhaseLineToLineSampledValueTemplateValue
,
397 fluctuationPhaseLineToLinePercent
400 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
401 voltagePhaseLineToLineValueRounded
,
404 meterValue
.sampledValue
.push(
406 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
407 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
409 phaseLineToLineValue
as MeterValuePhase
415 // Power.Active.Import measurand
416 powerSampledValueTemplate
= getSampledValueTemplate(
419 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
421 if (chargingStation
.getNumberOfPhases() === 3) {
422 powerPerPhaseSampledValueTemplates
= {
423 L1
: getSampledValueTemplate(
426 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
429 L2
: getSampledValueTemplate(
432 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
435 L3
: getSampledValueTemplate(
438 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
443 if (powerSampledValueTemplate
!= null) {
444 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
445 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
!)
446 const errMsg
= `MeterValues measurand ${
447 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
448 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
449 chargingStation.templateFile
450 }, cannot calculate ${
451 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
453 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
454 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
455 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
456 const connectorMaximumAvailablePower
=
457 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
458 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
459 const connectorMaximumPowerPerPhase
= Math.round(
460 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
462 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
463 const connectorMinimumPowerPerPhase
= Math.round(
464 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
466 switch (chargingStation
.stationInfo
?.currentOutType
) {
468 if (chargingStation
.getNumberOfPhases() === 3) {
469 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
470 powerSampledValueTemplate
.value
472 ? getRandomFloatFluctuatedRounded(
473 getLimitFromSampledValueTemplateCustomValue(
474 powerSampledValueTemplate
.value
,
475 connectorMaximumPower
/ unitDivider
,
476 connectorMinimumPower
/ unitDivider
,
479 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
480 fallbackValue
: connectorMinimumPower
/ unitDivider
482 ) / chargingStation
.getNumberOfPhases(),
483 powerSampledValueTemplate
.fluctuationPercent
??
484 Constants
.DEFAULT_FLUCTUATION_PERCENT
487 const phase1FluctuatedValue
= isNotEmptyString(
488 powerPerPhaseSampledValueTemplates
.L1
?.value
490 ? getRandomFloatFluctuatedRounded(
491 getLimitFromSampledValueTemplateCustomValue(
492 powerPerPhaseSampledValueTemplates
.L1
?.value
,
493 connectorMaximumPowerPerPhase
/ unitDivider
,
494 connectorMinimumPowerPerPhase
/ unitDivider
,
497 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
498 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
501 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
502 Constants
.DEFAULT_FLUCTUATION_PERCENT
505 const phase2FluctuatedValue
= isNotEmptyString(
506 powerPerPhaseSampledValueTemplates
.L2
?.value
508 ? getRandomFloatFluctuatedRounded(
509 getLimitFromSampledValueTemplateCustomValue(
510 powerPerPhaseSampledValueTemplates
.L2
?.value
,
511 connectorMaximumPowerPerPhase
/ unitDivider
,
512 connectorMinimumPowerPerPhase
/ unitDivider
,
515 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
516 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
519 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
520 Constants
.DEFAULT_FLUCTUATION_PERCENT
523 const phase3FluctuatedValue
= isNotEmptyString(
524 powerPerPhaseSampledValueTemplates
.L3
?.value
526 ? getRandomFloatFluctuatedRounded(
527 getLimitFromSampledValueTemplateCustomValue(
528 powerPerPhaseSampledValueTemplates
.L3
?.value
,
529 connectorMaximumPowerPerPhase
/ unitDivider
,
530 connectorMinimumPowerPerPhase
/ unitDivider
,
533 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
534 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
537 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
538 Constants
.DEFAULT_FLUCTUATION_PERCENT
541 powerMeasurandValues
.L1
=
542 phase1FluctuatedValue
??
543 defaultFluctuatedPowerPerPhase
??
544 getRandomFloatRounded(
545 connectorMaximumPowerPerPhase
/ unitDivider
,
546 connectorMinimumPowerPerPhase
/ unitDivider
548 powerMeasurandValues
.L2
=
549 phase2FluctuatedValue
??
550 defaultFluctuatedPowerPerPhase
??
551 getRandomFloatRounded(
552 connectorMaximumPowerPerPhase
/ unitDivider
,
553 connectorMinimumPowerPerPhase
/ unitDivider
555 powerMeasurandValues
.L3
=
556 phase3FluctuatedValue
??
557 defaultFluctuatedPowerPerPhase
??
558 getRandomFloatRounded(
559 connectorMaximumPowerPerPhase
/ unitDivider
,
560 connectorMinimumPowerPerPhase
/ unitDivider
563 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
564 ? getRandomFloatFluctuatedRounded(
565 getLimitFromSampledValueTemplateCustomValue(
566 powerSampledValueTemplate
.value
,
567 connectorMaximumPower
/ unitDivider
,
568 connectorMinimumPower
/ unitDivider
,
571 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
572 fallbackValue
: connectorMinimumPower
/ unitDivider
575 powerSampledValueTemplate
.fluctuationPercent
??
576 Constants
.DEFAULT_FLUCTUATION_PERCENT
578 : getRandomFloatRounded(
579 connectorMaximumPower
/ unitDivider
,
580 connectorMinimumPower
/ unitDivider
582 powerMeasurandValues
.L2
= 0
583 powerMeasurandValues
.L3
= 0
585 powerMeasurandValues
.allPhases
= roundTo(
586 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
591 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
592 ? getRandomFloatFluctuatedRounded(
593 getLimitFromSampledValueTemplateCustomValue(
594 powerSampledValueTemplate
.value
,
595 connectorMaximumPower
/ unitDivider
,
596 connectorMinimumPower
/ unitDivider
,
599 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
600 fallbackValue
: connectorMinimumPower
/ unitDivider
603 powerSampledValueTemplate
.fluctuationPercent
??
604 Constants
.DEFAULT_FLUCTUATION_PERCENT
606 : getRandomFloatRounded(
607 connectorMaximumPower
/ unitDivider
,
608 connectorMinimumPower
/ unitDivider
612 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
613 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
615 meterValue
.sampledValue
.push(
616 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
618 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
619 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
620 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
622 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
623 connectorMaximumPowerRounded
||
624 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
625 connectorMinimumPowerRounded
||
629 `${chargingStation.logPrefix()} MeterValues measurand ${
630 meterValue.sampledValue[sampledValuesIndex].measurand ??
631 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
632 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
633 meterValue.sampledValue[sampledValuesIndex].value
634 }/${connectorMaximumPowerRounded}`
639 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
642 const phaseValue
= `L${phase}-N`
643 meterValue
.sampledValue
.push(
645 powerPerPhaseSampledValueTemplates
[
646 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
647 ] ?? powerSampledValueTemplate
,
648 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
650 phaseValue
as MeterValuePhase
653 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
654 const connectorMaximumPowerPerPhaseRounded
= roundTo(
655 connectorMaximumPowerPerPhase
/ unitDivider
,
658 const connectorMinimumPowerPerPhaseRounded
= roundTo(
659 connectorMinimumPowerPerPhase
/ unitDivider
,
663 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
664 connectorMaximumPowerPerPhaseRounded
||
665 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
666 connectorMinimumPowerPerPhaseRounded
||
670 `${chargingStation.logPrefix()} MeterValues measurand ${
671 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
672 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
674 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
675 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
676 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
677 }/${connectorMaximumPowerPerPhaseRounded}`
682 // Current.Import measurand
683 currentSampledValueTemplate
= getSampledValueTemplate(
686 MeterValueMeasurand
.CURRENT_IMPORT
688 if (chargingStation
.getNumberOfPhases() === 3) {
689 currentPerPhaseSampledValueTemplates
= {
690 L1
: getSampledValueTemplate(
693 MeterValueMeasurand
.CURRENT_IMPORT
,
696 L2
: getSampledValueTemplate(
699 MeterValueMeasurand
.CURRENT_IMPORT
,
702 L3
: getSampledValueTemplate(
705 MeterValueMeasurand
.CURRENT_IMPORT
,
710 if (currentSampledValueTemplate
!= null) {
711 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
712 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
!)
713 const errMsg
= `MeterValues measurand ${
714 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
715 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
716 chargingStation.templateFile
717 }, cannot calculate ${
718 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
720 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
721 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
722 const connectorMaximumAvailablePower
=
723 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
724 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
725 let connectorMaximumAmperage
: number
726 switch (chargingStation
.stationInfo
?.currentOutType
) {
728 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
729 chargingStation
.getNumberOfPhases(),
730 connectorMaximumAvailablePower
,
731 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
732 chargingStation
.stationInfo
.voltageOut
!
734 if (chargingStation
.getNumberOfPhases() === 3) {
735 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
736 currentSampledValueTemplate
.value
738 ? getRandomFloatFluctuatedRounded(
739 getLimitFromSampledValueTemplateCustomValue(
740 currentSampledValueTemplate
.value
,
741 connectorMaximumAmperage
,
742 connectorMinimumAmperage
,
745 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
746 fallbackValue
: connectorMinimumAmperage
749 currentSampledValueTemplate
.fluctuationPercent
??
750 Constants
.DEFAULT_FLUCTUATION_PERCENT
753 const phase1FluctuatedValue
= isNotEmptyString(
754 currentPerPhaseSampledValueTemplates
.L1
?.value
756 ? getRandomFloatFluctuatedRounded(
757 getLimitFromSampledValueTemplateCustomValue(
758 currentPerPhaseSampledValueTemplates
.L1
?.value
,
759 connectorMaximumAmperage
,
760 connectorMinimumAmperage
,
763 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
764 fallbackValue
: connectorMinimumAmperage
767 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
768 Constants
.DEFAULT_FLUCTUATION_PERCENT
771 const phase2FluctuatedValue
= isNotEmptyString(
772 currentPerPhaseSampledValueTemplates
.L2
?.value
774 ? getRandomFloatFluctuatedRounded(
775 getLimitFromSampledValueTemplateCustomValue(
776 currentPerPhaseSampledValueTemplates
.L2
?.value
,
777 connectorMaximumAmperage
,
778 connectorMinimumAmperage
,
781 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
782 fallbackValue
: connectorMinimumAmperage
785 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
786 Constants
.DEFAULT_FLUCTUATION_PERCENT
789 const phase3FluctuatedValue
= isNotEmptyString(
790 currentPerPhaseSampledValueTemplates
.L3
?.value
792 ? getRandomFloatFluctuatedRounded(
793 getLimitFromSampledValueTemplateCustomValue(
794 currentPerPhaseSampledValueTemplates
.L3
?.value
,
795 connectorMaximumAmperage
,
796 connectorMinimumAmperage
,
799 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
800 fallbackValue
: connectorMinimumAmperage
803 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
804 Constants
.DEFAULT_FLUCTUATION_PERCENT
807 currentMeasurandValues
.L1
=
808 phase1FluctuatedValue
??
809 defaultFluctuatedAmperagePerPhase
??
810 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
811 currentMeasurandValues
.L2
=
812 phase2FluctuatedValue
??
813 defaultFluctuatedAmperagePerPhase
??
814 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
815 currentMeasurandValues
.L3
=
816 phase3FluctuatedValue
??
817 defaultFluctuatedAmperagePerPhase
??
818 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
820 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
821 ? getRandomFloatFluctuatedRounded(
822 getLimitFromSampledValueTemplateCustomValue(
823 currentSampledValueTemplate
.value
,
824 connectorMaximumAmperage
,
825 connectorMinimumAmperage
,
828 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
829 fallbackValue
: connectorMinimumAmperage
832 currentSampledValueTemplate
.fluctuationPercent
??
833 Constants
.DEFAULT_FLUCTUATION_PERCENT
835 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
836 currentMeasurandValues
.L2
= 0
837 currentMeasurandValues
.L3
= 0
839 currentMeasurandValues
.allPhases
= roundTo(
840 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
841 chargingStation
.getNumberOfPhases(),
846 connectorMaximumAmperage
= DCElectricUtils
.amperage(
847 connectorMaximumAvailablePower
,
848 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
849 chargingStation
.stationInfo
.voltageOut
!
851 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
852 ? getRandomFloatFluctuatedRounded(
853 getLimitFromSampledValueTemplateCustomValue(
854 currentSampledValueTemplate
.value
,
855 connectorMaximumAmperage
,
856 connectorMinimumAmperage
,
859 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
860 fallbackValue
: connectorMinimumAmperage
863 currentSampledValueTemplate
.fluctuationPercent
??
864 Constants
.DEFAULT_FLUCTUATION_PERCENT
866 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
869 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
870 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
872 meterValue
.sampledValue
.push(
873 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
875 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
877 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
878 connectorMaximumAmperage
||
879 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
880 connectorMinimumAmperage
||
884 `${chargingStation.logPrefix()} MeterValues measurand ${
885 meterValue.sampledValue[sampledValuesIndex].measurand ??
886 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
887 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
888 meterValue.sampledValue[sampledValuesIndex].value
889 }/${connectorMaximumAmperage}`
894 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
897 const phaseValue
= `L${phase}`
898 meterValue
.sampledValue
.push(
900 currentPerPhaseSampledValueTemplates
[
901 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
902 ] ?? currentSampledValueTemplate
,
903 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
905 phaseValue
as MeterValuePhase
908 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
910 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
911 connectorMaximumAmperage
||
912 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
913 connectorMinimumAmperage
||
917 `${chargingStation.logPrefix()} MeterValues measurand ${
918 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
919 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
921 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
922 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
923 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
924 }/${connectorMaximumAmperage}`
929 // Energy.Active.Import.Register measurand (default)
930 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
931 if (energySampledValueTemplate
!= null) {
932 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
933 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
!)
935 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
936 const connectorMaximumAvailablePower
=
937 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
938 const connectorMaximumEnergyRounded
= roundTo(
939 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
942 const connectorMinimumEnergyRounded
= roundTo(
943 energySampledValueTemplate
.minimumValue
?? 0,
946 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
947 ? getRandomFloatFluctuatedRounded(
948 getLimitFromSampledValueTemplateCustomValue(
949 energySampledValueTemplate
.value
,
950 connectorMaximumEnergyRounded
,
951 connectorMinimumEnergyRounded
,
953 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
954 fallbackValue
: connectorMinimumEnergyRounded
,
955 unitMultiplier
: unitDivider
958 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
960 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
961 // Persist previous value on connector
962 if (connector
!= null) {
964 !isNullOrUndefined(connector
.energyActiveImportRegisterValue
) &&
965 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
966 connector
.energyActiveImportRegisterValue
! >= 0 &&
967 !isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) &&
968 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
969 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
971 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
972 connector
.energyActiveImportRegisterValue
! += energyValueRounded
973 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
974 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
976 connector
.energyActiveImportRegisterValue
= 0
977 connector
.transactionEnergyActiveImportRegisterValue
= 0
980 meterValue
.sampledValue
.push(
982 energySampledValueTemplate
,
984 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
990 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
992 energyValueRounded
> connectorMaximumEnergyRounded
||
993 energyValueRounded
< connectorMinimumEnergyRounded
||
997 `${chargingStation.logPrefix()} MeterValues measurand ${
998 meterValue.sampledValue[sampledValuesIndex].measurand ??
999 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1000 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1005 case OCPPVersion
.VERSION_20
:
1006 case OCPPVersion
.VERSION_201
:
1008 throw new BaseError(
1009 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1014 export const buildTransactionEndMeterValue
= (
1015 chargingStation
: ChargingStation
,
1016 connectorId
: number,
1019 let meterValue
: MeterValue
1020 let sampledValueTemplate
: SampledValueTemplate
| undefined
1021 let unitDivider
: number
1022 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1023 case OCPPVersion
.VERSION_16
:
1025 timestamp
: new Date(),
1028 // Energy.Active.Import.Register measurand (default)
1029 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1030 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1031 meterValue
.sampledValue
.push(
1033 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1034 sampledValueTemplate
!,
1035 roundTo((meterStop
?? 0) / unitDivider
, 4),
1036 MeterValueContext
.TRANSACTION_END
1040 case OCPPVersion
.VERSION_20
:
1041 case OCPPVersion
.VERSION_201
:
1043 throw new BaseError(
1044 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1049 const checkMeasurandPowerDivider
= (
1050 chargingStation
: ChargingStation
,
1051 measurandType
: MeterValueMeasurand
1053 if (isUndefined(chargingStation
.powerDivider
)) {
1054 const errMsg
= `MeterValues measurand ${
1055 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1056 }: powerDivider is undefined`
1057 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1058 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1059 } else if (chargingStation
?.powerDivider
<= 0) {
1060 const errMsg
= `MeterValues measurand ${
1061 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1062 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1063 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1064 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1068 const getLimitFromSampledValueTemplateCustomValue
= (
1069 value
: string | undefined,
1072 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1076 limitationEnabled
: false,
1082 const parsedValue
= parseInt(value
?? '')
1083 if (options
?.limitationEnabled
=== true) {
1085 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1086 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1090 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1091 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1094 const getSampledValueTemplate
= (
1095 chargingStation
: ChargingStation
,
1096 connectorId
: number,
1097 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1098 phase
?: MeterValuePhase
1099 ): SampledValueTemplate
| undefined => {
1100 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1101 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1103 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1108 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1109 getConfigurationKey(
1111 StandardParametersKey
.MeterValuesSampledData
1112 )?.value
?.includes(measurand
) === false
1115 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1116 StandardParametersKey.MeterValuesSampledData
1121 const sampledValueTemplates
: SampledValueTemplate
[] =
1122 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1123 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1126 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1130 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1131 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1135 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1139 sampledValueTemplates
[index
]?.phase
=== phase
&&
1140 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1141 getConfigurationKey(
1143 StandardParametersKey
.MeterValuesSampledData
1144 )?.value
?.includes(measurand
) === true
1146 return sampledValueTemplates
[index
]
1149 sampledValueTemplates
[index
]?.phase
== null &&
1150 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1151 getConfigurationKey(
1153 StandardParametersKey
.MeterValuesSampledData
1154 )?.value
?.includes(measurand
) === true
1156 return sampledValueTemplates
[index
]
1158 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1159 (sampledValueTemplates
[index
]?.measurand
== null ||
1160 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1162 return sampledValueTemplates
[index
]
1165 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1166 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1167 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1168 throw new BaseError(errorMsg
)
1171 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1175 const buildSampledValue
= (
1176 sampledValueTemplate
: SampledValueTemplate
,
1178 context
?: MeterValueContext
,
1179 phase
?: MeterValuePhase
1180 ): SampledValue
=> {
1181 const sampledValueContext
= context
?? sampledValueTemplate
?.context
1182 const sampledValueLocation
=
1183 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1184 sampledValueTemplate
?.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1185 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
1186 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1188 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1189 unit
: sampledValueTemplate
.unit
1191 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1192 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1193 measurand
: sampledValueTemplate
.measurand
1195 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1196 ...(!isNullOrUndefined(value
) && { value
: value
.toString() }),
1197 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
})
1201 const getMeasurandDefaultLocation
= (
1202 measurandType
: MeterValueMeasurand
1203 ): MeterValueLocation
| undefined => {
1204 switch (measurandType
) {
1205 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1206 return MeterValueLocation
.EV
1210 // const getMeasurandDefaultUnit = (
1211 // measurandType: MeterValueMeasurand
1212 // ): MeterValueUnit | undefined => {
1213 // switch (measurandType) {
1214 // case MeterValueMeasurand.CURRENT_EXPORT:
1215 // case MeterValueMeasurand.CURRENT_IMPORT:
1216 // case MeterValueMeasurand.CURRENT_OFFERED:
1217 // return MeterValueUnit.AMP
1218 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1219 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1220 // return MeterValueUnit.WATT_HOUR
1221 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1222 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1223 // case MeterValueMeasurand.POWER_OFFERED:
1224 // return MeterValueUnit.WATT
1225 // case MeterValueMeasurand.STATE_OF_CHARGE:
1226 // return MeterValueUnit.PERCENT
1227 // case MeterValueMeasurand.VOLTAGE:
1228 // return MeterValueUnit.VOLT
1232 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1233 export class OCPPServiceUtils
{
1234 public static getMessageTypeString
= getMessageTypeString
1235 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1236 public static isIdTagAuthorized
= isIdTagAuthorized
1237 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1238 protected static getSampledValueTemplate
= getSampledValueTemplate
1239 protected static buildSampledValue
= buildSampledValue
1241 protected constructor () {
1242 // This is intentional
1245 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | null | undefined): ErrorType
{
1246 if (isNotEmptyArray(errors
)) {
1247 for (const error
of errors
as DefinedError
[]) {
1248 switch (error
.keyword
) {
1250 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1251 case 'dependencies':
1253 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1256 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1260 return ErrorType
.FORMAT_VIOLATION
1263 public static isRequestCommandSupported (
1264 chargingStation
: ChargingStation
,
1265 command
: RequestCommand
1267 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1270 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1275 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1277 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
]
1279 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1283 public static isIncomingRequestCommandSupported (
1284 chargingStation
: ChargingStation
,
1285 command
: IncomingRequestCommand
1287 const isIncomingRequestCommand
=
1288 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1290 isIncomingRequestCommand
&&
1291 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1295 isIncomingRequestCommand
&&
1296 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
?.[command
] != null
1298 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
]
1300 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1304 public static isMessageTriggerSupported (
1305 chargingStation
: ChargingStation
,
1306 messageTrigger
: MessageTrigger
1308 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1309 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1313 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1315 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
]
1318 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1323 public static isConnectorIdValid (
1324 chargingStation
: ChargingStation
,
1325 ocppCommand
: IncomingRequestCommand
,
1328 if (connectorId
< 0) {
1330 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1337 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1338 for (const key
in obj
) {
1339 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1340 if (isDate(obj
![key
])) {
1341 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1342 (obj
![key
] as string) = (obj
![key
] as Date).toISOString()
1343 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1344 } else if (obj
![key
] !== null && typeof obj
![key
] === 'object') {
1345 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1346 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
)
1351 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1352 if (chargingStation
.heartbeatSetInterval
== null) {
1353 chargingStation
.startHeartbeat()
1354 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1355 chargingStation
.restartHeartbeat()
1359 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1360 relativePath
: string,
1361 ocppVersion
: OCPPVersion
,
1362 moduleName
?: string,
1364 ): JSONSchemaType
<T
> {
1365 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1367 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1369 handleFileException(
1371 FileType
.JsonSchema
,
1372 error
as NodeJS
.ErrnoException
,
1373 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1374 { throwError
: false }
1376 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1377 return {} as JSONSchemaType
<T
>
1381 private static readonly logPrefix
= (
1382 ocppVersion
: OCPPVersion
,
1383 moduleName
?: string,
1387 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1388 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1389 : ` OCPP ${ocppVersion} |`
1390 return logPrefix(logMsg
)