1 import { randomInt
} from
'node:crypto'
2 import { readFileSync
} from
'node:fs'
3 import { dirname
, join
} from
'node:path'
4 import { fileURLToPath
} from
'node:url'
6 import type { DefinedError
, ErrorObject
, JSONSchemaType
} from
'ajv'
7 import { isDate
} from
'date-fns'
13 } from
'../../charging-station/index.js'
14 import { BaseError
, OCPPError
} from
'../../exception/index.js'
17 type AuthorizeRequest
,
18 type AuthorizeResponse
,
20 ChargingStationEvents
,
26 IncomingRequestCommand
,
28 type MeasurandPerPhaseSampledValueTemplates
,
38 type OCPP16ChargePointStatus
,
39 type OCPP16StatusNotificationRequest
,
40 type OCPP20ConnectorStatusEnumType
,
41 type OCPP20StatusNotificationRequest
,
45 type SampledValueTemplate
,
46 StandardParametersKey
,
47 type StatusNotificationRequest
,
48 type StatusNotificationResponse
49 } from
'../../types/index.js'
56 getRandomFloatFluctuatedRounded
,
57 getRandomFloatRounded
,
66 } from
'../../utils/index.js'
67 import { OCPP16Constants
} from
'./1.6/OCPP16Constants.js'
68 import { OCPP20Constants
} from
'./2.0/OCPP20Constants.js'
69 import { OCPPConstants
} from
'./OCPPConstants.js'
71 export const getMessageTypeString
= (messageType
: MessageType
| undefined): string => {
72 switch (messageType
) {
73 case MessageType
.CALL_MESSAGE
:
75 case MessageType
.CALL_RESULT_MESSAGE
:
77 case MessageType
.CALL_ERROR_MESSAGE
:
84 const buildStatusNotificationRequest
= (
85 chargingStation
: ChargingStation
,
87 status: ConnectorStatusEnum
,
89 ): StatusNotificationRequest
=> {
90 switch (chargingStation
.stationInfo
?.ocppVersion
) {
91 case OCPPVersion
.VERSION_16
:
94 status: status as OCPP16ChargePointStatus
,
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 as OCPP20ConnectorStatusEnumType
,
103 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
105 } satisfies OCPP20StatusNotificationRequest
107 throw new BaseError('Cannot build status notification payload: OCPP version not supported')
111 export const isIdTagAuthorized
= async (
112 chargingStation
: ChargingStation
,
115 ): Promise
<boolean> => {
117 !chargingStation
.getLocalAuthListEnabled() &&
118 chargingStation
.stationInfo
?.remoteAuthorization
=== false
121 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
124 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
126 connectorStatus
!= null &&
127 chargingStation
.getLocalAuthListEnabled() &&
128 isIdTagLocalAuthorized(chargingStation
, idTag
)
130 connectorStatus
.localAuthorizeIdTag
= idTag
131 connectorStatus
.idTagLocalAuthorized
= true
133 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
134 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
139 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
141 chargingStation
.hasIdTags() &&
143 chargingStation
.idTagsCache
144 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
145 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
!)!)
146 ?.find(tag
=> tag
=== idTag
)
151 const isIdTagRemoteAuthorized
= async (
152 chargingStation
: ChargingStation
,
155 ): Promise
<boolean> => {
156 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
157 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
160 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
162 RequestCommand
.AUTHORIZE
,
167 ).idTagInfo
.status === AuthorizationStatus
.ACCEPTED
171 export const sendAndSetConnectorStatus
= async (
172 chargingStation
: ChargingStation
,
174 status: ConnectorStatusEnum
,
176 options
?: { send
: boolean }
177 ): Promise
<void> => {
178 options
= { send
: true, ...options
}
180 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
181 await chargingStation
.ocppRequestService
.requestHandler
<
182 StatusNotificationRequest
,
183 StatusNotificationResponse
186 RequestCommand
.STATUS_NOTIFICATION
,
187 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
190 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
191 chargingStation
.getConnectorStatus(connectorId
)!.status = status
192 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
194 ...chargingStation
.getConnectorStatus(connectorId
)
198 export const restoreConnectorStatus
= async (
199 chargingStation
: ChargingStation
,
201 connectorStatus
: ConnectorStatus
| undefined
202 ): Promise
<void> => {
204 connectorStatus
?.reservation
!= null &&
205 connectorStatus
.status !== ConnectorStatusEnum
.Reserved
207 await sendAndSetConnectorStatus(chargingStation
, connectorId
, ConnectorStatusEnum
.Reserved
)
208 } else if (connectorStatus
?.status !== ConnectorStatusEnum
.Available
) {
209 await sendAndSetConnectorStatus(chargingStation
, connectorId
, ConnectorStatusEnum
.Available
)
213 const checkConnectorStatusTransition
= (
214 chargingStation
: ChargingStation
,
216 status: ConnectorStatusEnum
218 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)?.status
219 let transitionAllowed
= false
220 switch (chargingStation
.stationInfo
?.ocppVersion
) {
221 case OCPPVersion
.VERSION_16
:
223 (connectorId
=== 0 &&
224 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
225 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
228 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
229 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
232 transitionAllowed
= true
235 case OCPPVersion
.VERSION_20
:
236 case OCPPVersion
.VERSION_201
:
238 (connectorId
=== 0 &&
239 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
240 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
243 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
244 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
247 transitionAllowed
= true
252 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
255 if (!transitionAllowed
) {
257 `${chargingStation.logPrefix()} OCPP ${
258 chargingStation.stationInfo.ocppVersion
259 } connector id ${connectorId} status transition from '${
260 chargingStation.getConnectorStatus(connectorId)?.status
261 }' to '${status}' is not allowed`
264 return transitionAllowed
267 export const buildMeterValue
= (
268 chargingStation
: ChargingStation
,
270 transactionId
: number,
274 const connector
= chargingStation
.getConnectorStatus(connectorId
)
275 let meterValue
: MeterValue
276 let socSampledValueTemplate
: SampledValueTemplate
| undefined
277 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
278 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
279 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
280 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
281 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
282 let energySampledValueTemplate
: SampledValueTemplate
| undefined
283 switch (chargingStation
.stationInfo
?.ocppVersion
) {
284 case OCPPVersion
.VERSION_16
:
286 timestamp
: new Date(),
290 socSampledValueTemplate
= getSampledValueTemplate(
293 MeterValueMeasurand
.STATE_OF_CHARGE
295 if (socSampledValueTemplate
!= null) {
296 const socMaximumValue
= 100
297 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
298 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
299 ? getRandomFloatFluctuatedRounded(
300 parseInt(socSampledValueTemplate
.value
),
301 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
303 : randomInt(socMinimumValue
, socMaximumValue
)
304 meterValue
.sampledValue
.push(
305 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
307 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
309 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
310 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
314 `${chargingStation.logPrefix()} MeterValues measurand ${
315 meterValue.sampledValue[sampledValuesIndex].measurand ??
316 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
317 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
318 meterValue.sampledValue[sampledValuesIndex].value
319 }/${socMaximumValue}`
324 voltageSampledValueTemplate
= getSampledValueTemplate(
327 MeterValueMeasurand
.VOLTAGE
329 if (voltageSampledValueTemplate
!= null) {
330 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
331 ? parseInt(voltageSampledValueTemplate
.value
)
332 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
333 chargingStation
.stationInfo
.voltageOut
!
334 const fluctuationPercent
=
335 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
336 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
337 voltageSampledValueTemplateValue
,
341 chargingStation
.getNumberOfPhases() !== 3 ||
342 (chargingStation
.getNumberOfPhases() === 3 &&
343 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
345 meterValue
.sampledValue
.push(
346 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
351 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
354 const phaseLineToNeutralValue
= `L${phase}-N`
355 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
358 MeterValueMeasurand
.VOLTAGE
,
359 phaseLineToNeutralValue
as MeterValuePhase
361 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
362 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
363 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
364 voltagePhaseLineToNeutralSampledValueTemplate
.value
366 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
367 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
368 chargingStation
.stationInfo
.voltageOut
!
369 const fluctuationPhaseToNeutralPercent
=
370 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
371 Constants
.DEFAULT_FLUCTUATION_PERCENT
372 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
373 voltagePhaseLineToNeutralSampledValueTemplateValue
,
374 fluctuationPhaseToNeutralPercent
377 meterValue
.sampledValue
.push(
379 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
380 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
382 phaseLineToNeutralValue
as MeterValuePhase
385 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
386 const phaseLineToLineValue
= `L${phase}-L${
387 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
388 ? (phase + 1) % chargingStation.getNumberOfPhases()
389 : chargingStation.getNumberOfPhases()
391 const voltagePhaseLineToLineValueRounded
= roundTo(
392 Math.sqrt(chargingStation
.getNumberOfPhases()) *
393 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
394 chargingStation
.stationInfo
.voltageOut
!,
397 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
400 MeterValueMeasurand
.VOLTAGE
,
401 phaseLineToLineValue
as MeterValuePhase
403 let voltagePhaseLineToLineMeasurandValue
: number | undefined
404 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
405 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
406 voltagePhaseLineToLineSampledValueTemplate
.value
408 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
409 : voltagePhaseLineToLineValueRounded
410 const fluctuationPhaseLineToLinePercent
=
411 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
412 Constants
.DEFAULT_FLUCTUATION_PERCENT
413 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
414 voltagePhaseLineToLineSampledValueTemplateValue
,
415 fluctuationPhaseLineToLinePercent
418 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
419 voltagePhaseLineToLineValueRounded
,
422 meterValue
.sampledValue
.push(
424 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
425 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
427 phaseLineToLineValue
as MeterValuePhase
433 // Power.Active.Import measurand
434 powerSampledValueTemplate
= getSampledValueTemplate(
437 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
439 if (chargingStation
.getNumberOfPhases() === 3) {
440 powerPerPhaseSampledValueTemplates
= {
441 L1
: getSampledValueTemplate(
444 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
447 L2
: getSampledValueTemplate(
450 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
453 L3
: getSampledValueTemplate(
456 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
461 if (powerSampledValueTemplate
!= null) {
462 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
463 const errMsg
= `MeterValues measurand ${
464 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
465 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
466 chargingStation.templateFile
467 }, cannot calculate ${
468 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
470 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
471 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
472 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
473 const connectorMaximumAvailablePower
=
474 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
475 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
476 const connectorMaximumPowerPerPhase
= Math.round(
477 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
479 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
480 const connectorMinimumPowerPerPhase
= Math.round(
481 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
483 switch (chargingStation
.stationInfo
.currentOutType
) {
485 if (chargingStation
.getNumberOfPhases() === 3) {
486 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
487 powerSampledValueTemplate
.value
489 ? getRandomFloatFluctuatedRounded(
490 getLimitFromSampledValueTemplateCustomValue(
491 powerSampledValueTemplate
.value
,
492 connectorMaximumPower
/ unitDivider
,
493 connectorMinimumPower
/ unitDivider
,
496 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
497 fallbackValue
: connectorMinimumPower
/ unitDivider
499 ) / chargingStation
.getNumberOfPhases(),
500 powerSampledValueTemplate
.fluctuationPercent
??
501 Constants
.DEFAULT_FLUCTUATION_PERCENT
504 const phase1FluctuatedValue
= isNotEmptyString(
505 powerPerPhaseSampledValueTemplates
.L1
?.value
507 ? getRandomFloatFluctuatedRounded(
508 getLimitFromSampledValueTemplateCustomValue(
509 powerPerPhaseSampledValueTemplates
.L1
.value
,
510 connectorMaximumPowerPerPhase
/ unitDivider
,
511 connectorMinimumPowerPerPhase
/ unitDivider
,
514 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
515 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
518 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
519 Constants
.DEFAULT_FLUCTUATION_PERCENT
522 const phase2FluctuatedValue
= isNotEmptyString(
523 powerPerPhaseSampledValueTemplates
.L2
?.value
525 ? getRandomFloatFluctuatedRounded(
526 getLimitFromSampledValueTemplateCustomValue(
527 powerPerPhaseSampledValueTemplates
.L2
.value
,
528 connectorMaximumPowerPerPhase
/ unitDivider
,
529 connectorMinimumPowerPerPhase
/ unitDivider
,
532 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
533 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
536 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
537 Constants
.DEFAULT_FLUCTUATION_PERCENT
540 const phase3FluctuatedValue
= isNotEmptyString(
541 powerPerPhaseSampledValueTemplates
.L3
?.value
543 ? getRandomFloatFluctuatedRounded(
544 getLimitFromSampledValueTemplateCustomValue(
545 powerPerPhaseSampledValueTemplates
.L3
.value
,
546 connectorMaximumPowerPerPhase
/ unitDivider
,
547 connectorMinimumPowerPerPhase
/ unitDivider
,
550 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
551 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
554 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
555 Constants
.DEFAULT_FLUCTUATION_PERCENT
558 powerMeasurandValues
.L1
=
559 phase1FluctuatedValue
??
560 defaultFluctuatedPowerPerPhase
??
561 getRandomFloatRounded(
562 connectorMaximumPowerPerPhase
/ unitDivider
,
563 connectorMinimumPowerPerPhase
/ unitDivider
565 powerMeasurandValues
.L2
=
566 phase2FluctuatedValue
??
567 defaultFluctuatedPowerPerPhase
??
568 getRandomFloatRounded(
569 connectorMaximumPowerPerPhase
/ unitDivider
,
570 connectorMinimumPowerPerPhase
/ unitDivider
572 powerMeasurandValues
.L3
=
573 phase3FluctuatedValue
??
574 defaultFluctuatedPowerPerPhase
??
575 getRandomFloatRounded(
576 connectorMaximumPowerPerPhase
/ unitDivider
,
577 connectorMinimumPowerPerPhase
/ unitDivider
580 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
581 ? getRandomFloatFluctuatedRounded(
582 getLimitFromSampledValueTemplateCustomValue(
583 powerSampledValueTemplate
.value
,
584 connectorMaximumPower
/ unitDivider
,
585 connectorMinimumPower
/ unitDivider
,
588 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
589 fallbackValue
: connectorMinimumPower
/ unitDivider
592 powerSampledValueTemplate
.fluctuationPercent
??
593 Constants
.DEFAULT_FLUCTUATION_PERCENT
595 : getRandomFloatRounded(
596 connectorMaximumPower
/ unitDivider
,
597 connectorMinimumPower
/ unitDivider
599 powerMeasurandValues
.L2
= 0
600 powerMeasurandValues
.L3
= 0
602 powerMeasurandValues
.allPhases
= roundTo(
603 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
608 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
609 ? getRandomFloatFluctuatedRounded(
610 getLimitFromSampledValueTemplateCustomValue(
611 powerSampledValueTemplate
.value
,
612 connectorMaximumPower
/ unitDivider
,
613 connectorMinimumPower
/ unitDivider
,
616 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
617 fallbackValue
: connectorMinimumPower
/ unitDivider
620 powerSampledValueTemplate
.fluctuationPercent
??
621 Constants
.DEFAULT_FLUCTUATION_PERCENT
623 : getRandomFloatRounded(
624 connectorMaximumPower
/ unitDivider
,
625 connectorMinimumPower
/ unitDivider
629 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
630 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
632 meterValue
.sampledValue
.push(
633 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
635 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
636 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
637 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
639 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
640 connectorMaximumPowerRounded
||
641 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
642 connectorMinimumPowerRounded
||
646 `${chargingStation.logPrefix()} MeterValues measurand ${
647 meterValue.sampledValue[sampledValuesIndex].measurand ??
648 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
649 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
650 meterValue.sampledValue[sampledValuesIndex].value
651 }/${connectorMaximumPowerRounded}`
656 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
659 const phaseValue
= `L${phase}-N`
660 meterValue
.sampledValue
.push(
662 powerPerPhaseSampledValueTemplates
[
663 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
664 ] ?? powerSampledValueTemplate
,
665 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
667 phaseValue
as MeterValuePhase
670 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
671 const connectorMaximumPowerPerPhaseRounded
= roundTo(
672 connectorMaximumPowerPerPhase
/ unitDivider
,
675 const connectorMinimumPowerPerPhaseRounded
= roundTo(
676 connectorMinimumPowerPerPhase
/ unitDivider
,
680 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
681 connectorMaximumPowerPerPhaseRounded
||
682 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
683 connectorMinimumPowerPerPhaseRounded
||
687 `${chargingStation.logPrefix()} MeterValues measurand ${
688 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
689 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
691 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
692 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
693 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
694 }/${connectorMaximumPowerPerPhaseRounded}`
699 // Current.Import measurand
700 currentSampledValueTemplate
= getSampledValueTemplate(
703 MeterValueMeasurand
.CURRENT_IMPORT
705 if (chargingStation
.getNumberOfPhases() === 3) {
706 currentPerPhaseSampledValueTemplates
= {
707 L1
: getSampledValueTemplate(
710 MeterValueMeasurand
.CURRENT_IMPORT
,
713 L2
: getSampledValueTemplate(
716 MeterValueMeasurand
.CURRENT_IMPORT
,
719 L3
: getSampledValueTemplate(
722 MeterValueMeasurand
.CURRENT_IMPORT
,
727 if (currentSampledValueTemplate
!= null) {
728 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
729 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
730 const errMsg
= `MeterValues measurand ${
731 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
732 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
733 chargingStation.templateFile
734 }, cannot calculate ${
735 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
737 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
738 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
739 const connectorMaximumAvailablePower
=
740 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
741 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
742 let connectorMaximumAmperage
: number
743 switch (chargingStation
.stationInfo
.currentOutType
) {
745 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
746 chargingStation
.getNumberOfPhases(),
747 connectorMaximumAvailablePower
,
748 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
749 chargingStation
.stationInfo
.voltageOut
!
751 if (chargingStation
.getNumberOfPhases() === 3) {
752 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
753 currentSampledValueTemplate
.value
755 ? getRandomFloatFluctuatedRounded(
756 getLimitFromSampledValueTemplateCustomValue(
757 currentSampledValueTemplate
.value
,
758 connectorMaximumAmperage
,
759 connectorMinimumAmperage
,
762 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
763 fallbackValue
: connectorMinimumAmperage
766 currentSampledValueTemplate
.fluctuationPercent
??
767 Constants
.DEFAULT_FLUCTUATION_PERCENT
770 const phase1FluctuatedValue
= isNotEmptyString(
771 currentPerPhaseSampledValueTemplates
.L1
?.value
773 ? getRandomFloatFluctuatedRounded(
774 getLimitFromSampledValueTemplateCustomValue(
775 currentPerPhaseSampledValueTemplates
.L1
.value
,
776 connectorMaximumAmperage
,
777 connectorMinimumAmperage
,
780 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
781 fallbackValue
: connectorMinimumAmperage
784 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
785 Constants
.DEFAULT_FLUCTUATION_PERCENT
788 const phase2FluctuatedValue
= isNotEmptyString(
789 currentPerPhaseSampledValueTemplates
.L2
?.value
791 ? getRandomFloatFluctuatedRounded(
792 getLimitFromSampledValueTemplateCustomValue(
793 currentPerPhaseSampledValueTemplates
.L2
.value
,
794 connectorMaximumAmperage
,
795 connectorMinimumAmperage
,
798 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
799 fallbackValue
: connectorMinimumAmperage
802 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
803 Constants
.DEFAULT_FLUCTUATION_PERCENT
806 const phase3FluctuatedValue
= isNotEmptyString(
807 currentPerPhaseSampledValueTemplates
.L3
?.value
809 ? getRandomFloatFluctuatedRounded(
810 getLimitFromSampledValueTemplateCustomValue(
811 currentPerPhaseSampledValueTemplates
.L3
.value
,
812 connectorMaximumAmperage
,
813 connectorMinimumAmperage
,
816 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
817 fallbackValue
: connectorMinimumAmperage
820 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
821 Constants
.DEFAULT_FLUCTUATION_PERCENT
824 currentMeasurandValues
.L1
=
825 phase1FluctuatedValue
??
826 defaultFluctuatedAmperagePerPhase
??
827 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
828 currentMeasurandValues
.L2
=
829 phase2FluctuatedValue
??
830 defaultFluctuatedAmperagePerPhase
??
831 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
832 currentMeasurandValues
.L3
=
833 phase3FluctuatedValue
??
834 defaultFluctuatedAmperagePerPhase
??
835 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
837 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
838 ? getRandomFloatFluctuatedRounded(
839 getLimitFromSampledValueTemplateCustomValue(
840 currentSampledValueTemplate
.value
,
841 connectorMaximumAmperage
,
842 connectorMinimumAmperage
,
845 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
846 fallbackValue
: connectorMinimumAmperage
849 currentSampledValueTemplate
.fluctuationPercent
??
850 Constants
.DEFAULT_FLUCTUATION_PERCENT
852 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
853 currentMeasurandValues
.L2
= 0
854 currentMeasurandValues
.L3
= 0
856 currentMeasurandValues
.allPhases
= roundTo(
857 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
858 chargingStation
.getNumberOfPhases(),
863 connectorMaximumAmperage
= DCElectricUtils
.amperage(
864 connectorMaximumAvailablePower
,
865 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
866 chargingStation
.stationInfo
.voltageOut
!
868 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
869 ? getRandomFloatFluctuatedRounded(
870 getLimitFromSampledValueTemplateCustomValue(
871 currentSampledValueTemplate
.value
,
872 connectorMaximumAmperage
,
873 connectorMinimumAmperage
,
876 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
877 fallbackValue
: connectorMinimumAmperage
880 currentSampledValueTemplate
.fluctuationPercent
??
881 Constants
.DEFAULT_FLUCTUATION_PERCENT
883 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
886 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
887 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
889 meterValue
.sampledValue
.push(
890 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
892 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
894 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
895 connectorMaximumAmperage
||
896 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
897 connectorMinimumAmperage
||
901 `${chargingStation.logPrefix()} MeterValues measurand ${
902 meterValue.sampledValue[sampledValuesIndex].measurand ??
903 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
904 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
905 meterValue.sampledValue[sampledValuesIndex].value
906 }/${connectorMaximumAmperage}`
911 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
914 const phaseValue
= `L${phase}`
915 meterValue
.sampledValue
.push(
917 currentPerPhaseSampledValueTemplates
[
918 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
919 ] ?? currentSampledValueTemplate
,
920 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
922 phaseValue
as MeterValuePhase
925 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
927 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
928 connectorMaximumAmperage
||
929 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
930 connectorMinimumAmperage
||
934 `${chargingStation.logPrefix()} MeterValues measurand ${
935 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
936 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
938 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
939 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
940 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
941 }/${connectorMaximumAmperage}`
946 // Energy.Active.Import.Register measurand (default)
947 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
948 if (energySampledValueTemplate
!= null) {
949 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
951 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
952 const connectorMaximumAvailablePower
=
953 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
954 const connectorMaximumEnergyRounded
= roundTo(
955 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
958 const connectorMinimumEnergyRounded
= roundTo(
959 energySampledValueTemplate
.minimumValue
?? 0,
962 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
963 ? getRandomFloatFluctuatedRounded(
964 getLimitFromSampledValueTemplateCustomValue(
965 energySampledValueTemplate
.value
,
966 connectorMaximumEnergyRounded
,
967 connectorMinimumEnergyRounded
,
969 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
970 fallbackValue
: connectorMinimumEnergyRounded
,
971 unitMultiplier
: unitDivider
974 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
976 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
977 // Persist previous value on connector
978 if (connector
!= null) {
980 connector
.energyActiveImportRegisterValue
!= null &&
981 connector
.energyActiveImportRegisterValue
>= 0 &&
982 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
983 connector
.transactionEnergyActiveImportRegisterValue
>= 0
985 connector
.energyActiveImportRegisterValue
+= energyValueRounded
986 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
988 connector
.energyActiveImportRegisterValue
= 0
989 connector
.transactionEnergyActiveImportRegisterValue
= 0
992 meterValue
.sampledValue
.push(
994 energySampledValueTemplate
,
996 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
1002 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
1004 energyValueRounded
> connectorMaximumEnergyRounded
||
1005 energyValueRounded
< connectorMinimumEnergyRounded
||
1009 `${chargingStation.logPrefix()} MeterValues measurand ${
1010 meterValue.sampledValue[sampledValuesIndex].measurand ??
1011 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1012 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1017 case OCPPVersion
.VERSION_20
:
1018 case OCPPVersion
.VERSION_201
:
1020 throw new BaseError(
1021 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1026 export const buildTransactionEndMeterValue
= (
1027 chargingStation
: ChargingStation
,
1028 connectorId
: number,
1029 meterStop
: number | undefined
1031 let meterValue
: MeterValue
1032 let sampledValueTemplate
: SampledValueTemplate
| undefined
1033 let unitDivider
: number
1034 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1035 case OCPPVersion
.VERSION_16
:
1037 timestamp
: new Date(),
1040 // Energy.Active.Import.Register measurand (default)
1041 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1042 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1043 meterValue
.sampledValue
.push(
1045 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1046 sampledValueTemplate
!,
1047 roundTo((meterStop
?? 0) / unitDivider
, 4),
1048 MeterValueContext
.TRANSACTION_END
1052 case OCPPVersion
.VERSION_20
:
1053 case OCPPVersion
.VERSION_201
:
1055 throw new BaseError(
1056 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1061 const checkMeasurandPowerDivider
= (
1062 chargingStation
: ChargingStation
,
1063 measurandType
: MeterValueMeasurand
| undefined
1065 if (chargingStation
.powerDivider
== null) {
1066 const errMsg
= `MeterValues measurand ${
1067 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1068 }: powerDivider is undefined`
1069 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1070 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1071 } else if (chargingStation
.powerDivider
<= 0) {
1072 const errMsg
= `MeterValues measurand ${
1073 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1074 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1075 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1076 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1080 const getLimitFromSampledValueTemplateCustomValue
= (
1081 value
: string | undefined,
1084 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1088 limitationEnabled
: false,
1094 const parsedValue
= parseInt(value
?? '')
1095 if (options
.limitationEnabled
=== true) {
1097 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1098 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1102 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1103 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1106 const getSampledValueTemplate
= (
1107 chargingStation
: ChargingStation
,
1108 connectorId
: number,
1109 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1110 phase
?: MeterValuePhase
1111 ): SampledValueTemplate
| undefined => {
1112 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1113 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1115 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1120 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1121 getConfigurationKey(
1123 StandardParametersKey
.MeterValuesSampledData
1124 )?.value
?.includes(measurand
) === false
1127 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1128 StandardParametersKey.MeterValuesSampledData
1133 const sampledValueTemplates
=
1134 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1135 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1138 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1142 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1143 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1147 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1151 sampledValueTemplates
[index
]?.phase
=== phase
&&
1152 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1153 getConfigurationKey(
1155 StandardParametersKey
.MeterValuesSampledData
1156 )?.value
?.includes(measurand
) === true
1158 return sampledValueTemplates
[index
]
1161 sampledValueTemplates
[index
]?.phase
== null &&
1162 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1163 getConfigurationKey(
1165 StandardParametersKey
.MeterValuesSampledData
1166 )?.value
?.includes(measurand
) === true
1168 return sampledValueTemplates
[index
]
1170 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1171 (sampledValueTemplates
[index
]?.measurand
== null ||
1172 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1174 return sampledValueTemplates
[index
]
1177 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1178 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1179 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1180 throw new BaseError(errorMsg
)
1183 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1187 const buildSampledValue
= (
1188 sampledValueTemplate
: SampledValueTemplate
,
1190 context
?: MeterValueContext
,
1191 phase
?: MeterValuePhase
1192 ): SampledValue
=> {
1193 const sampledValueContext
= context
?? sampledValueTemplate
.context
1194 const sampledValueLocation
=
1195 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1196 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1197 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1199 ...(sampledValueTemplate
.unit
!= null && {
1200 unit
: sampledValueTemplate
.unit
1202 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1203 ...(sampledValueTemplate
.measurand
!= null && {
1204 measurand
: sampledValueTemplate
.measurand
1206 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1207 ...{ value
: value
.toString() },
1208 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1209 } satisfies SampledValue
1212 const getMeasurandDefaultLocation
= (
1213 measurandType
: MeterValueMeasurand
1214 ): MeterValueLocation
| undefined => {
1215 switch (measurandType
) {
1216 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1217 return MeterValueLocation
.EV
1221 // const getMeasurandDefaultUnit = (
1222 // measurandType: MeterValueMeasurand
1223 // ): MeterValueUnit | undefined => {
1224 // switch (measurandType) {
1225 // case MeterValueMeasurand.CURRENT_EXPORT:
1226 // case MeterValueMeasurand.CURRENT_IMPORT:
1227 // case MeterValueMeasurand.CURRENT_OFFERED:
1228 // return MeterValueUnit.AMP
1229 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1230 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1231 // return MeterValueUnit.WATT_HOUR
1232 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1233 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1234 // case MeterValueMeasurand.POWER_OFFERED:
1235 // return MeterValueUnit.WATT
1236 // case MeterValueMeasurand.STATE_OF_CHARGE:
1237 // return MeterValueUnit.PERCENT
1238 // case MeterValueMeasurand.VOLTAGE:
1239 // return MeterValueUnit.VOLT
1243 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1244 export class OCPPServiceUtils
{
1245 public static readonly getMessageTypeString
= getMessageTypeString
1246 public static readonly sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1247 public static readonly restoreConnectorStatus
= restoreConnectorStatus
1248 public static readonly isIdTagAuthorized
= isIdTagAuthorized
1249 public static readonly buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1250 protected static getSampledValueTemplate
= getSampledValueTemplate
1251 protected static buildSampledValue
= buildSampledValue
1253 protected constructor () {
1254 // This is intentional
1257 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | undefined | null): ErrorType
{
1258 if (isNotEmptyArray(errors
)) {
1259 for (const error
of errors
as DefinedError
[]) {
1260 switch (error
.keyword
) {
1262 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1263 case 'dependencies':
1265 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1268 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1272 return ErrorType
.FORMAT_VIOLATION
1275 public static isRequestCommandSupported (
1276 chargingStation
: ChargingStation
,
1277 command
: RequestCommand
1279 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1282 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1287 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1289 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1291 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1295 public static isIncomingRequestCommandSupported (
1296 chargingStation
: ChargingStation
,
1297 command
: IncomingRequestCommand
1299 const isIncomingRequestCommand
=
1300 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1302 isIncomingRequestCommand
&&
1303 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1307 isIncomingRequestCommand
&&
1308 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1310 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1312 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1316 public static isMessageTriggerSupported (
1317 chargingStation
: ChargingStation
,
1318 messageTrigger
: MessageTrigger
1320 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1321 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1325 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1327 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1330 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1335 public static isConnectorIdValid (
1336 chargingStation
: ChargingStation
,
1337 ocppCommand
: IncomingRequestCommand
,
1340 if (connectorId
< 0) {
1342 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1349 public static convertDateToISOString
<T
extends JsonType
>(object
: T
): void {
1350 for (const key
in object
) {
1351 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1352 if (isDate(object
![key
])) {
1353 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1354 (object
![key
] as string) = (object
![key
] as Date).toISOString()
1355 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
1356 } else if (typeof object
![key
] === 'object' && object
![key
] !== null) {
1357 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1358 OCPPServiceUtils
.convertDateToISOString
<T
>(object
![key
] as T
)
1363 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1364 if (chargingStation
.heartbeatSetInterval
== null) {
1365 chargingStation
.startHeartbeat()
1366 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1367 chargingStation
.restartHeartbeat()
1371 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1372 relativePath
: string,
1373 ocppVersion
: OCPPVersion
,
1374 moduleName
?: string,
1376 ): JSONSchemaType
<T
> {
1377 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1379 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1381 handleFileException(
1383 FileType
.JsonSchema
,
1384 error
as NodeJS
.ErrnoException
,
1385 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1386 { throwError
: false }
1388 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1389 return {} as JSONSchemaType
<T
>
1393 private static readonly logPrefix
= (
1394 ocppVersion
: OCPPVersion
,
1395 moduleName
?: string,
1399 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1400 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1401 : ` OCPP ${ocppVersion} |`
1402 return logPrefix(logMsg
)