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
,
68 } from
'../../utils/index.js'
70 export const getMessageTypeString
= (messageType
: MessageType
): string => {
71 switch (messageType
) {
72 case MessageType
.CALL_MESSAGE
:
74 case MessageType
.CALL_RESULT_MESSAGE
:
76 case MessageType
.CALL_ERROR_MESSAGE
:
83 export const buildStatusNotificationRequest
= (
84 chargingStation
: ChargingStation
,
86 status: ConnectorStatusEnum
,
88 ): StatusNotificationRequest
=> {
89 switch (chargingStation
.stationInfo
?.ocppVersion
) {
90 case OCPPVersion
.VERSION_16
:
94 errorCode
: ChargePointErrorCode
.NO_ERROR
95 } satisfies OCPP16StatusNotificationRequest
96 case OCPPVersion
.VERSION_20
:
97 case OCPPVersion
.VERSION_201
:
99 timestamp
: new Date(),
100 connectorStatus
: status,
103 } satisfies OCPP20StatusNotificationRequest
105 throw new BaseError('Cannot build status notification payload: OCPP version not supported')
109 export const isIdTagAuthorized
= async (
110 chargingStation
: ChargingStation
,
113 ): Promise
<boolean> => {
115 !chargingStation
.getLocalAuthListEnabled() &&
116 chargingStation
.stationInfo
?.remoteAuthorization
=== false
119 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
122 if (chargingStation
.getLocalAuthListEnabled() && isIdTagLocalAuthorized(chargingStation
, idTag
)) {
123 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
124 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
125 connectorStatus
.localAuthorizeIdTag
= idTag
126 connectorStatus
.idTagLocalAuthorized
= true
128 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
129 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
134 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
136 chargingStation
.hasIdTags() &&
138 chargingStation
.idTagsCache
139 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
140 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
141 ?.find((tag
) => tag
=== idTag
)
146 const isIdTagRemoteAuthorized
= async (
147 chargingStation
: ChargingStation
,
150 ): Promise
<boolean> => {
151 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
152 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
155 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
157 RequestCommand
.AUTHORIZE
,
162 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
166 export const sendAndSetConnectorStatus
= async (
167 chargingStation
: ChargingStation
,
169 status: ConnectorStatusEnum
,
171 options
?: { send
: boolean }
172 ): Promise
<void> => {
173 options
= { send
: true, ...options
}
175 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
176 await chargingStation
.ocppRequestService
.requestHandler
<
177 StatusNotificationRequest
,
178 StatusNotificationResponse
181 RequestCommand
.STATUS_NOTIFICATION
,
182 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
185 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
186 chargingStation
.getConnectorStatus(connectorId
)!.status = status
187 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
189 ...chargingStation
.getConnectorStatus(connectorId
)
193 const checkConnectorStatusTransition
= (
194 chargingStation
: ChargingStation
,
196 status: ConnectorStatusEnum
198 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
199 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status
200 let transitionAllowed
= false
201 switch (chargingStation
.stationInfo
?.ocppVersion
) {
202 case OCPPVersion
.VERSION_16
:
204 (connectorId
=== 0 &&
205 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
206 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
209 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
210 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
213 transitionAllowed
= true
216 case OCPPVersion
.VERSION_20
:
217 case OCPPVersion
.VERSION_201
:
219 (connectorId
=== 0 &&
220 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
221 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
224 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
225 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
228 transitionAllowed
= true
233 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
236 if (!transitionAllowed
) {
238 `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo
239 ?.ocppVersion} connector id ${connectorId} status transition from '${
240 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
241 chargingStation.getConnectorStatus(connectorId)!.status
242 }' to '${status}' is not allowed`
245 return transitionAllowed
248 export const buildMeterValue
= (
249 chargingStation
: ChargingStation
,
251 transactionId
: number,
255 const connector
= chargingStation
.getConnectorStatus(connectorId
)
256 let meterValue
: MeterValue
257 let socSampledValueTemplate
: SampledValueTemplate
| undefined
258 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
259 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
260 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
261 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
262 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
263 let energySampledValueTemplate
: SampledValueTemplate
| undefined
264 switch (chargingStation
.stationInfo
?.ocppVersion
) {
265 case OCPPVersion
.VERSION_16
:
267 timestamp
: new Date(),
271 socSampledValueTemplate
= getSampledValueTemplate(
274 MeterValueMeasurand
.STATE_OF_CHARGE
276 if (socSampledValueTemplate
!= null) {
277 const socMaximumValue
= 100
278 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
279 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
280 ? getRandomFloatFluctuatedRounded(
281 parseInt(socSampledValueTemplate
.value
),
282 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
284 : getRandomInteger(socMaximumValue
, socMinimumValue
)
285 meterValue
.sampledValue
.push(
286 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
288 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
290 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
291 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
295 `${chargingStation.logPrefix()} MeterValues measurand ${
296 meterValue.sampledValue[sampledValuesIndex].measurand ??
297 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
298 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
299 meterValue.sampledValue[sampledValuesIndex].value
300 }/${socMaximumValue}`
305 voltageSampledValueTemplate
= getSampledValueTemplate(
308 MeterValueMeasurand
.VOLTAGE
310 if (voltageSampledValueTemplate
!= null) {
311 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
312 ? parseInt(voltageSampledValueTemplate
.value
)
313 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
314 chargingStation
.stationInfo
.voltageOut
!
315 const fluctuationPercent
=
316 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
317 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
318 voltageSampledValueTemplateValue
,
322 chargingStation
.getNumberOfPhases() !== 3 ||
323 (chargingStation
.getNumberOfPhases() === 3 &&
324 chargingStation
.stationInfo
?.mainVoltageMeterValues
=== true)
326 meterValue
.sampledValue
.push(
327 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
332 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
335 const phaseLineToNeutralValue
= `L${phase}-N`
336 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
339 MeterValueMeasurand
.VOLTAGE
,
340 phaseLineToNeutralValue
as MeterValuePhase
342 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
343 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
344 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
345 voltagePhaseLineToNeutralSampledValueTemplate
.value
347 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
348 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
349 chargingStation
.stationInfo
.voltageOut
!
350 const fluctuationPhaseToNeutralPercent
=
351 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
352 Constants
.DEFAULT_FLUCTUATION_PERCENT
353 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
354 voltagePhaseLineToNeutralSampledValueTemplateValue
,
355 fluctuationPhaseToNeutralPercent
358 meterValue
.sampledValue
.push(
360 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
361 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
363 phaseLineToNeutralValue
as MeterValuePhase
366 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
=== true) {
367 const phaseLineToLineValue
= `L${phase}-L${
368 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
369 ? (phase + 1) % chargingStation.getNumberOfPhases()
370 : chargingStation.getNumberOfPhases()
372 const voltagePhaseLineToLineValueRounded
= roundTo(
373 Math.sqrt(chargingStation
.getNumberOfPhases()) *
374 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
375 chargingStation
.stationInfo
.voltageOut
!,
378 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
381 MeterValueMeasurand
.VOLTAGE
,
382 phaseLineToLineValue
as MeterValuePhase
384 let voltagePhaseLineToLineMeasurandValue
: number | undefined
385 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
386 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
387 voltagePhaseLineToLineSampledValueTemplate
.value
389 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
390 : voltagePhaseLineToLineValueRounded
391 const fluctuationPhaseLineToLinePercent
=
392 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
393 Constants
.DEFAULT_FLUCTUATION_PERCENT
394 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
395 voltagePhaseLineToLineSampledValueTemplateValue
,
396 fluctuationPhaseLineToLinePercent
399 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
400 voltagePhaseLineToLineValueRounded
,
403 meterValue
.sampledValue
.push(
405 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
406 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
408 phaseLineToLineValue
as MeterValuePhase
414 // Power.Active.Import measurand
415 powerSampledValueTemplate
= getSampledValueTemplate(
418 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
420 if (chargingStation
.getNumberOfPhases() === 3) {
421 powerPerPhaseSampledValueTemplates
= {
422 L1
: getSampledValueTemplate(
425 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
428 L2
: getSampledValueTemplate(
431 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
434 L3
: getSampledValueTemplate(
437 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
442 if (powerSampledValueTemplate
!= null) {
443 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
444 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
!)
445 const errMsg
= `MeterValues measurand ${
446 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
447 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
448 chargingStation.templateFile
449 }, cannot calculate ${
450 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
452 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
453 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
454 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
455 const connectorMaximumAvailablePower
=
456 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
457 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
458 const connectorMaximumPowerPerPhase
= Math.round(
459 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
461 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
462 const connectorMinimumPowerPerPhase
= Math.round(
463 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
465 switch (chargingStation
.stationInfo
?.currentOutType
) {
467 if (chargingStation
.getNumberOfPhases() === 3) {
468 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
469 powerSampledValueTemplate
.value
471 ? getRandomFloatFluctuatedRounded(
472 getLimitFromSampledValueTemplateCustomValue(
473 powerSampledValueTemplate
.value
,
474 connectorMaximumPower
/ unitDivider
,
475 connectorMinimumPower
/ unitDivider
,
478 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
479 fallbackValue
: connectorMinimumPower
/ unitDivider
481 ) / chargingStation
.getNumberOfPhases(),
482 powerSampledValueTemplate
.fluctuationPercent
??
483 Constants
.DEFAULT_FLUCTUATION_PERCENT
486 const phase1FluctuatedValue
= isNotEmptyString(
487 powerPerPhaseSampledValueTemplates
.L1
?.value
489 ? getRandomFloatFluctuatedRounded(
490 getLimitFromSampledValueTemplateCustomValue(
491 powerPerPhaseSampledValueTemplates
.L1
?.value
,
492 connectorMaximumPowerPerPhase
/ unitDivider
,
493 connectorMinimumPowerPerPhase
/ unitDivider
,
496 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
497 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
500 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
501 Constants
.DEFAULT_FLUCTUATION_PERCENT
504 const phase2FluctuatedValue
= isNotEmptyString(
505 powerPerPhaseSampledValueTemplates
.L2
?.value
507 ? getRandomFloatFluctuatedRounded(
508 getLimitFromSampledValueTemplateCustomValue(
509 powerPerPhaseSampledValueTemplates
.L2
?.value
,
510 connectorMaximumPowerPerPhase
/ unitDivider
,
511 connectorMinimumPowerPerPhase
/ unitDivider
,
514 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
515 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
518 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
519 Constants
.DEFAULT_FLUCTUATION_PERCENT
522 const phase3FluctuatedValue
= isNotEmptyString(
523 powerPerPhaseSampledValueTemplates
.L3
?.value
525 ? getRandomFloatFluctuatedRounded(
526 getLimitFromSampledValueTemplateCustomValue(
527 powerPerPhaseSampledValueTemplates
.L3
?.value
,
528 connectorMaximumPowerPerPhase
/ unitDivider
,
529 connectorMinimumPowerPerPhase
/ unitDivider
,
532 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
533 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
536 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
537 Constants
.DEFAULT_FLUCTUATION_PERCENT
540 powerMeasurandValues
.L1
=
541 phase1FluctuatedValue
??
542 defaultFluctuatedPowerPerPhase
??
543 getRandomFloatRounded(
544 connectorMaximumPowerPerPhase
/ unitDivider
,
545 connectorMinimumPowerPerPhase
/ unitDivider
547 powerMeasurandValues
.L2
=
548 phase2FluctuatedValue
??
549 defaultFluctuatedPowerPerPhase
??
550 getRandomFloatRounded(
551 connectorMaximumPowerPerPhase
/ unitDivider
,
552 connectorMinimumPowerPerPhase
/ unitDivider
554 powerMeasurandValues
.L3
=
555 phase3FluctuatedValue
??
556 defaultFluctuatedPowerPerPhase
??
557 getRandomFloatRounded(
558 connectorMaximumPowerPerPhase
/ unitDivider
,
559 connectorMinimumPowerPerPhase
/ unitDivider
562 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
563 ? getRandomFloatFluctuatedRounded(
564 getLimitFromSampledValueTemplateCustomValue(
565 powerSampledValueTemplate
.value
,
566 connectorMaximumPower
/ unitDivider
,
567 connectorMinimumPower
/ unitDivider
,
570 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
571 fallbackValue
: connectorMinimumPower
/ unitDivider
574 powerSampledValueTemplate
.fluctuationPercent
??
575 Constants
.DEFAULT_FLUCTUATION_PERCENT
577 : getRandomFloatRounded(
578 connectorMaximumPower
/ unitDivider
,
579 connectorMinimumPower
/ unitDivider
581 powerMeasurandValues
.L2
= 0
582 powerMeasurandValues
.L3
= 0
584 powerMeasurandValues
.allPhases
= roundTo(
585 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
590 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
591 ? getRandomFloatFluctuatedRounded(
592 getLimitFromSampledValueTemplateCustomValue(
593 powerSampledValueTemplate
.value
,
594 connectorMaximumPower
/ unitDivider
,
595 connectorMinimumPower
/ unitDivider
,
598 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
599 fallbackValue
: connectorMinimumPower
/ unitDivider
602 powerSampledValueTemplate
.fluctuationPercent
??
603 Constants
.DEFAULT_FLUCTUATION_PERCENT
605 : getRandomFloatRounded(
606 connectorMaximumPower
/ unitDivider
,
607 connectorMinimumPower
/ unitDivider
611 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
612 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
614 meterValue
.sampledValue
.push(
615 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
617 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
618 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
619 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
621 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
622 connectorMaximumPowerRounded
||
623 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
624 connectorMinimumPowerRounded
||
628 `${chargingStation.logPrefix()} MeterValues measurand ${
629 meterValue.sampledValue[sampledValuesIndex].measurand ??
630 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
631 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
632 meterValue.sampledValue[sampledValuesIndex].value
633 }/${connectorMaximumPowerRounded}`
638 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
641 const phaseValue
= `L${phase}-N`
642 meterValue
.sampledValue
.push(
644 powerPerPhaseSampledValueTemplates
[
645 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
646 ] ?? powerSampledValueTemplate
,
647 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
649 phaseValue
as MeterValuePhase
652 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
653 const connectorMaximumPowerPerPhaseRounded
= roundTo(
654 connectorMaximumPowerPerPhase
/ unitDivider
,
657 const connectorMinimumPowerPerPhaseRounded
= roundTo(
658 connectorMinimumPowerPerPhase
/ unitDivider
,
662 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
663 connectorMaximumPowerPerPhaseRounded
||
664 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
665 connectorMinimumPowerPerPhaseRounded
||
669 `${chargingStation.logPrefix()} MeterValues measurand ${
670 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
671 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
673 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
674 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
675 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
676 }/${connectorMaximumPowerPerPhaseRounded}`
681 // Current.Import measurand
682 currentSampledValueTemplate
= getSampledValueTemplate(
685 MeterValueMeasurand
.CURRENT_IMPORT
687 if (chargingStation
.getNumberOfPhases() === 3) {
688 currentPerPhaseSampledValueTemplates
= {
689 L1
: getSampledValueTemplate(
692 MeterValueMeasurand
.CURRENT_IMPORT
,
695 L2
: getSampledValueTemplate(
698 MeterValueMeasurand
.CURRENT_IMPORT
,
701 L3
: getSampledValueTemplate(
704 MeterValueMeasurand
.CURRENT_IMPORT
,
709 if (currentSampledValueTemplate
!= null) {
710 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
711 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
!)
712 const errMsg
= `MeterValues measurand ${
713 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
714 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
715 chargingStation.templateFile
716 }, cannot calculate ${
717 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
719 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
720 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
721 const connectorMaximumAvailablePower
=
722 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
723 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
724 let connectorMaximumAmperage
: number
725 switch (chargingStation
.stationInfo
?.currentOutType
) {
727 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
728 chargingStation
.getNumberOfPhases(),
729 connectorMaximumAvailablePower
,
730 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
731 chargingStation
.stationInfo
.voltageOut
!
733 if (chargingStation
.getNumberOfPhases() === 3) {
734 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
735 currentSampledValueTemplate
.value
737 ? getRandomFloatFluctuatedRounded(
738 getLimitFromSampledValueTemplateCustomValue(
739 currentSampledValueTemplate
.value
,
740 connectorMaximumAmperage
,
741 connectorMinimumAmperage
,
744 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
745 fallbackValue
: connectorMinimumAmperage
748 currentSampledValueTemplate
.fluctuationPercent
??
749 Constants
.DEFAULT_FLUCTUATION_PERCENT
752 const phase1FluctuatedValue
= isNotEmptyString(
753 currentPerPhaseSampledValueTemplates
.L1
?.value
755 ? getRandomFloatFluctuatedRounded(
756 getLimitFromSampledValueTemplateCustomValue(
757 currentPerPhaseSampledValueTemplates
.L1
?.value
,
758 connectorMaximumAmperage
,
759 connectorMinimumAmperage
,
762 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
763 fallbackValue
: connectorMinimumAmperage
766 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
767 Constants
.DEFAULT_FLUCTUATION_PERCENT
770 const phase2FluctuatedValue
= isNotEmptyString(
771 currentPerPhaseSampledValueTemplates
.L2
?.value
773 ? getRandomFloatFluctuatedRounded(
774 getLimitFromSampledValueTemplateCustomValue(
775 currentPerPhaseSampledValueTemplates
.L2
?.value
,
776 connectorMaximumAmperage
,
777 connectorMinimumAmperage
,
780 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
781 fallbackValue
: connectorMinimumAmperage
784 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
785 Constants
.DEFAULT_FLUCTUATION_PERCENT
788 const phase3FluctuatedValue
= isNotEmptyString(
789 currentPerPhaseSampledValueTemplates
.L3
?.value
791 ? getRandomFloatFluctuatedRounded(
792 getLimitFromSampledValueTemplateCustomValue(
793 currentPerPhaseSampledValueTemplates
.L3
?.value
,
794 connectorMaximumAmperage
,
795 connectorMinimumAmperage
,
798 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
799 fallbackValue
: connectorMinimumAmperage
802 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
803 Constants
.DEFAULT_FLUCTUATION_PERCENT
806 currentMeasurandValues
.L1
=
807 phase1FluctuatedValue
??
808 defaultFluctuatedAmperagePerPhase
??
809 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
810 currentMeasurandValues
.L2
=
811 phase2FluctuatedValue
??
812 defaultFluctuatedAmperagePerPhase
??
813 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
814 currentMeasurandValues
.L3
=
815 phase3FluctuatedValue
??
816 defaultFluctuatedAmperagePerPhase
??
817 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
819 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
820 ? getRandomFloatFluctuatedRounded(
821 getLimitFromSampledValueTemplateCustomValue(
822 currentSampledValueTemplate
.value
,
823 connectorMaximumAmperage
,
824 connectorMinimumAmperage
,
827 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
828 fallbackValue
: connectorMinimumAmperage
831 currentSampledValueTemplate
.fluctuationPercent
??
832 Constants
.DEFAULT_FLUCTUATION_PERCENT
834 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
835 currentMeasurandValues
.L2
= 0
836 currentMeasurandValues
.L3
= 0
838 currentMeasurandValues
.allPhases
= roundTo(
839 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
840 chargingStation
.getNumberOfPhases(),
845 connectorMaximumAmperage
= DCElectricUtils
.amperage(
846 connectorMaximumAvailablePower
,
847 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
848 chargingStation
.stationInfo
.voltageOut
!
850 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
851 ? getRandomFloatFluctuatedRounded(
852 getLimitFromSampledValueTemplateCustomValue(
853 currentSampledValueTemplate
.value
,
854 connectorMaximumAmperage
,
855 connectorMinimumAmperage
,
858 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
859 fallbackValue
: connectorMinimumAmperage
862 currentSampledValueTemplate
.fluctuationPercent
??
863 Constants
.DEFAULT_FLUCTUATION_PERCENT
865 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
868 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
869 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
871 meterValue
.sampledValue
.push(
872 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
874 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
876 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
877 connectorMaximumAmperage
||
878 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
879 connectorMinimumAmperage
||
883 `${chargingStation.logPrefix()} MeterValues measurand ${
884 meterValue.sampledValue[sampledValuesIndex].measurand ??
885 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
886 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
887 meterValue.sampledValue[sampledValuesIndex].value
888 }/${connectorMaximumAmperage}`
893 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
896 const phaseValue
= `L${phase}`
897 meterValue
.sampledValue
.push(
899 currentPerPhaseSampledValueTemplates
[
900 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
901 ] ?? currentSampledValueTemplate
,
902 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
904 phaseValue
as MeterValuePhase
907 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
909 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
910 connectorMaximumAmperage
||
911 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
912 connectorMinimumAmperage
||
916 `${chargingStation.logPrefix()} MeterValues measurand ${
917 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
918 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
920 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
921 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
922 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
923 }/${connectorMaximumAmperage}`
928 // Energy.Active.Import.Register measurand (default)
929 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
930 if (energySampledValueTemplate
!= null) {
931 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
932 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
!)
934 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
935 const connectorMaximumAvailablePower
=
936 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
937 const connectorMaximumEnergyRounded
= roundTo(
938 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
941 const connectorMinimumEnergyRounded
= roundTo(
942 energySampledValueTemplate
.minimumValue
?? 0,
945 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
946 ? getRandomFloatFluctuatedRounded(
947 getLimitFromSampledValueTemplateCustomValue(
948 energySampledValueTemplate
.value
,
949 connectorMaximumEnergyRounded
,
950 connectorMinimumEnergyRounded
,
952 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
953 fallbackValue
: connectorMinimumEnergyRounded
,
954 unitMultiplier
: unitDivider
957 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
959 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
960 // Persist previous value on connector
961 if (connector
!= null) {
963 connector
.energyActiveImportRegisterValue
!= null &&
964 connector
.energyActiveImportRegisterValue
>= 0 &&
965 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
966 connector
.transactionEnergyActiveImportRegisterValue
>= 0
968 connector
.energyActiveImportRegisterValue
+= energyValueRounded
969 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
971 connector
.energyActiveImportRegisterValue
= 0
972 connector
.transactionEnergyActiveImportRegisterValue
= 0
975 meterValue
.sampledValue
.push(
977 energySampledValueTemplate
,
979 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
985 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
987 energyValueRounded
> connectorMaximumEnergyRounded
||
988 energyValueRounded
< connectorMinimumEnergyRounded
||
992 `${chargingStation.logPrefix()} MeterValues measurand ${
993 meterValue.sampledValue[sampledValuesIndex].measurand ??
994 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
995 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1000 case OCPPVersion
.VERSION_20
:
1001 case OCPPVersion
.VERSION_201
:
1003 throw new BaseError(
1004 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1009 export const buildTransactionEndMeterValue
= (
1010 chargingStation
: ChargingStation
,
1011 connectorId
: number,
1014 let meterValue
: MeterValue
1015 let sampledValueTemplate
: SampledValueTemplate
| undefined
1016 let unitDivider
: number
1017 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1018 case OCPPVersion
.VERSION_16
:
1020 timestamp
: new Date(),
1023 // Energy.Active.Import.Register measurand (default)
1024 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1025 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1026 meterValue
.sampledValue
.push(
1028 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1029 sampledValueTemplate
!,
1030 roundTo((meterStop
?? 0) / unitDivider
, 4),
1031 MeterValueContext
.TRANSACTION_END
1035 case OCPPVersion
.VERSION_20
:
1036 case OCPPVersion
.VERSION_201
:
1038 throw new BaseError(
1039 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1044 const checkMeasurandPowerDivider
= (
1045 chargingStation
: ChargingStation
,
1046 measurandType
: MeterValueMeasurand
1048 if (isUndefined(chargingStation
.powerDivider
)) {
1049 const errMsg
= `MeterValues measurand ${
1050 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1051 }: powerDivider is undefined`
1052 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1053 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1054 } else if (chargingStation
?.powerDivider
<= 0) {
1055 const errMsg
= `MeterValues measurand ${
1056 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1057 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1058 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1059 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1063 const getLimitFromSampledValueTemplateCustomValue
= (
1064 value
: string | undefined,
1067 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1071 limitationEnabled
: false,
1077 const parsedValue
= parseInt(value
?? '')
1078 if (options
?.limitationEnabled
=== true) {
1080 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1081 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1085 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1086 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1089 const getSampledValueTemplate
= (
1090 chargingStation
: ChargingStation
,
1091 connectorId
: number,
1092 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1093 phase
?: MeterValuePhase
1094 ): SampledValueTemplate
| undefined => {
1095 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1096 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1098 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1103 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1104 getConfigurationKey(
1106 StandardParametersKey
.MeterValuesSampledData
1107 )?.value
?.includes(measurand
) === false
1110 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1111 StandardParametersKey.MeterValuesSampledData
1116 const sampledValueTemplates
: SampledValueTemplate
[] =
1117 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1118 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1121 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1125 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1126 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1130 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1134 sampledValueTemplates
[index
]?.phase
=== phase
&&
1135 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1136 getConfigurationKey(
1138 StandardParametersKey
.MeterValuesSampledData
1139 )?.value
?.includes(measurand
) === true
1141 return sampledValueTemplates
[index
]
1144 sampledValueTemplates
[index
]?.phase
== null &&
1145 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1146 getConfigurationKey(
1148 StandardParametersKey
.MeterValuesSampledData
1149 )?.value
?.includes(measurand
) === true
1151 return sampledValueTemplates
[index
]
1153 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1154 (sampledValueTemplates
[index
]?.measurand
== null ||
1155 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1157 return sampledValueTemplates
[index
]
1160 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1161 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1162 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1163 throw new BaseError(errorMsg
)
1166 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1170 const buildSampledValue
= (
1171 sampledValueTemplate
: SampledValueTemplate
,
1173 context
?: MeterValueContext
,
1174 phase
?: MeterValuePhase
1175 ): SampledValue
=> {
1176 const sampledValueContext
= context
?? sampledValueTemplate
?.context
1177 const sampledValueLocation
=
1178 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1179 sampledValueTemplate
?.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1180 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
1181 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1183 ...(sampledValueTemplate
.unit
!= null && {
1184 unit
: sampledValueTemplate
.unit
1186 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1187 ...(sampledValueTemplate
.measurand
!= null && {
1188 measurand
: sampledValueTemplate
.measurand
1190 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1191 ...(value
!= null && { value
: value
.toString() }),
1192 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1196 const getMeasurandDefaultLocation
= (
1197 measurandType
: MeterValueMeasurand
1198 ): MeterValueLocation
| undefined => {
1199 switch (measurandType
) {
1200 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1201 return MeterValueLocation
.EV
1205 // const getMeasurandDefaultUnit = (
1206 // measurandType: MeterValueMeasurand
1207 // ): MeterValueUnit | undefined => {
1208 // switch (measurandType) {
1209 // case MeterValueMeasurand.CURRENT_EXPORT:
1210 // case MeterValueMeasurand.CURRENT_IMPORT:
1211 // case MeterValueMeasurand.CURRENT_OFFERED:
1212 // return MeterValueUnit.AMP
1213 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1214 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1215 // return MeterValueUnit.WATT_HOUR
1216 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1217 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1218 // case MeterValueMeasurand.POWER_OFFERED:
1219 // return MeterValueUnit.WATT
1220 // case MeterValueMeasurand.STATE_OF_CHARGE:
1221 // return MeterValueUnit.PERCENT
1222 // case MeterValueMeasurand.VOLTAGE:
1223 // return MeterValueUnit.VOLT
1227 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1228 export class OCPPServiceUtils
{
1229 public static getMessageTypeString
= getMessageTypeString
1230 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1231 public static isIdTagAuthorized
= isIdTagAuthorized
1232 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1233 protected static getSampledValueTemplate
= getSampledValueTemplate
1234 protected static buildSampledValue
= buildSampledValue
1236 protected constructor () {
1237 // This is intentional
1240 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | null | undefined): ErrorType
{
1241 if (isNotEmptyArray(errors
)) {
1242 for (const error
of errors
as DefinedError
[]) {
1243 switch (error
.keyword
) {
1245 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1246 case 'dependencies':
1248 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1251 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1255 return ErrorType
.FORMAT_VIOLATION
1258 public static isRequestCommandSupported (
1259 chargingStation
: ChargingStation
,
1260 command
: RequestCommand
1262 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1265 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1270 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1272 return chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
[command
]
1274 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1278 public static isIncomingRequestCommandSupported (
1279 chargingStation
: ChargingStation
,
1280 command
: IncomingRequestCommand
1282 const isIncomingRequestCommand
=
1283 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1285 isIncomingRequestCommand
&&
1286 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1290 isIncomingRequestCommand
&&
1291 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
?.[command
] != null
1293 return chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
]
1295 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1299 public static isMessageTriggerSupported (
1300 chargingStation
: ChargingStation
,
1301 messageTrigger
: MessageTrigger
1303 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1304 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1308 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1310 return chargingStation
.stationInfo
?.messageTriggerSupport
[messageTrigger
]
1313 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1318 public static isConnectorIdValid (
1319 chargingStation
: ChargingStation
,
1320 ocppCommand
: IncomingRequestCommand
,
1323 if (connectorId
< 0) {
1325 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1332 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1333 for (const key
in obj
) {
1334 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1335 if (isDate(obj
![key
])) {
1336 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1337 (obj
![key
] as string) = (obj
![key
] as Date).toISOString()
1338 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1339 } else if (obj
![key
] !== null && typeof obj
![key
] === 'object') {
1340 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1341 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
)
1346 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1347 if (chargingStation
.heartbeatSetInterval
== null) {
1348 chargingStation
.startHeartbeat()
1349 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1350 chargingStation
.restartHeartbeat()
1354 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1355 relativePath
: string,
1356 ocppVersion
: OCPPVersion
,
1357 moduleName
?: string,
1359 ): JSONSchemaType
<T
> {
1360 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1362 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1364 handleFileException(
1366 FileType
.JsonSchema
,
1367 error
as NodeJS
.ErrnoException
,
1368 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1369 { throwError
: false }
1371 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1372 return {} as JSONSchemaType
<T
>
1376 private static readonly logPrefix
= (
1377 ocppVersion
: OCPPVersion
,
1378 moduleName
?: string,
1382 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1383 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1384 : ` OCPP ${ocppVersion} |`
1385 return logPrefix(logMsg
)