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 Number.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 ${
318 connector?.transactionId
319 }, value: ${socMinimumValue}/${
320 meterValue.sampledValue[sampledValuesIndex].value
321 }/${socMaximumValue}`
326 voltageSampledValueTemplate
= getSampledValueTemplate(
329 MeterValueMeasurand
.VOLTAGE
331 if (voltageSampledValueTemplate
!= null) {
332 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
333 ? Number.parseInt(voltageSampledValueTemplate
.value
)
334 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
335 chargingStation
.stationInfo
.voltageOut
!
336 const fluctuationPercent
=
337 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
338 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
339 voltageSampledValueTemplateValue
,
343 chargingStation
.getNumberOfPhases() !== 3 ||
344 (chargingStation
.getNumberOfPhases() === 3 &&
345 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
347 meterValue
.sampledValue
.push(
348 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
353 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
356 const phaseLineToNeutralValue
= `L${phase}-N`
357 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
360 MeterValueMeasurand
.VOLTAGE
,
361 phaseLineToNeutralValue
as MeterValuePhase
363 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
364 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
365 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
366 voltagePhaseLineToNeutralSampledValueTemplate
.value
368 ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
369 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
370 chargingStation
.stationInfo
.voltageOut
!
371 const fluctuationPhaseToNeutralPercent
=
372 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
373 Constants
.DEFAULT_FLUCTUATION_PERCENT
374 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
375 voltagePhaseLineToNeutralSampledValueTemplateValue
,
376 fluctuationPhaseToNeutralPercent
379 meterValue
.sampledValue
.push(
381 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
382 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
384 phaseLineToNeutralValue
as MeterValuePhase
387 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
388 const phaseLineToLineValue
= `L${phase}-L${
389 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
390 ? (phase + 1) % chargingStation.getNumberOfPhases()
391 : chargingStation.getNumberOfPhases()
393 const voltagePhaseLineToLineValueRounded
= roundTo(
394 Math.sqrt(chargingStation
.getNumberOfPhases()) *
395 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
396 chargingStation
.stationInfo
.voltageOut
!,
399 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
402 MeterValueMeasurand
.VOLTAGE
,
403 phaseLineToLineValue
as MeterValuePhase
405 let voltagePhaseLineToLineMeasurandValue
: number | undefined
406 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
407 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
408 voltagePhaseLineToLineSampledValueTemplate
.value
410 ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
411 : voltagePhaseLineToLineValueRounded
412 const fluctuationPhaseLineToLinePercent
=
413 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
414 Constants
.DEFAULT_FLUCTUATION_PERCENT
415 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
416 voltagePhaseLineToLineSampledValueTemplateValue
,
417 fluctuationPhaseLineToLinePercent
420 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
421 voltagePhaseLineToLineValueRounded
,
424 meterValue
.sampledValue
.push(
426 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
427 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
429 phaseLineToLineValue
as MeterValuePhase
435 // Power.Active.Import measurand
436 powerSampledValueTemplate
= getSampledValueTemplate(
439 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
441 if (chargingStation
.getNumberOfPhases() === 3) {
442 powerPerPhaseSampledValueTemplates
= {
443 L1
: getSampledValueTemplate(
446 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
449 L2
: getSampledValueTemplate(
452 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
455 L3
: getSampledValueTemplate(
458 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
463 if (powerSampledValueTemplate
!= null) {
464 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
465 const errMsg
= `MeterValues measurand ${
466 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
467 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
468 chargingStation.templateFile
469 }, cannot calculate ${
470 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
472 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
473 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
474 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
475 const connectorMaximumAvailablePower
=
476 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
477 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
478 const connectorMaximumPowerPerPhase
= Math.round(
479 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
481 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
482 const connectorMinimumPowerPerPhase
= Math.round(
483 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
485 switch (chargingStation
.stationInfo
.currentOutType
) {
487 if (chargingStation
.getNumberOfPhases() === 3) {
488 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
489 powerSampledValueTemplate
.value
491 ? getRandomFloatFluctuatedRounded(
492 getLimitFromSampledValueTemplateCustomValue(
493 powerSampledValueTemplate
.value
,
494 connectorMaximumPower
/ unitDivider
,
495 connectorMinimumPower
/ unitDivider
,
498 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
499 fallbackValue
: connectorMinimumPower
/ unitDivider
501 ) / chargingStation
.getNumberOfPhases(),
502 powerSampledValueTemplate
.fluctuationPercent
??
503 Constants
.DEFAULT_FLUCTUATION_PERCENT
506 const phase1FluctuatedValue
= isNotEmptyString(
507 powerPerPhaseSampledValueTemplates
.L1
?.value
509 ? getRandomFloatFluctuatedRounded(
510 getLimitFromSampledValueTemplateCustomValue(
511 powerPerPhaseSampledValueTemplates
.L1
.value
,
512 connectorMaximumPowerPerPhase
/ unitDivider
,
513 connectorMinimumPowerPerPhase
/ unitDivider
,
516 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
517 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
520 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
521 Constants
.DEFAULT_FLUCTUATION_PERCENT
524 const phase2FluctuatedValue
= isNotEmptyString(
525 powerPerPhaseSampledValueTemplates
.L2
?.value
527 ? getRandomFloatFluctuatedRounded(
528 getLimitFromSampledValueTemplateCustomValue(
529 powerPerPhaseSampledValueTemplates
.L2
.value
,
530 connectorMaximumPowerPerPhase
/ unitDivider
,
531 connectorMinimumPowerPerPhase
/ unitDivider
,
534 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
535 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
538 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
539 Constants
.DEFAULT_FLUCTUATION_PERCENT
542 const phase3FluctuatedValue
= isNotEmptyString(
543 powerPerPhaseSampledValueTemplates
.L3
?.value
545 ? getRandomFloatFluctuatedRounded(
546 getLimitFromSampledValueTemplateCustomValue(
547 powerPerPhaseSampledValueTemplates
.L3
.value
,
548 connectorMaximumPowerPerPhase
/ unitDivider
,
549 connectorMinimumPowerPerPhase
/ unitDivider
,
552 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
553 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
556 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
557 Constants
.DEFAULT_FLUCTUATION_PERCENT
560 powerMeasurandValues
.L1
=
561 phase1FluctuatedValue
??
562 defaultFluctuatedPowerPerPhase
??
563 getRandomFloatRounded(
564 connectorMaximumPowerPerPhase
/ unitDivider
,
565 connectorMinimumPowerPerPhase
/ unitDivider
567 powerMeasurandValues
.L2
=
568 phase2FluctuatedValue
??
569 defaultFluctuatedPowerPerPhase
??
570 getRandomFloatRounded(
571 connectorMaximumPowerPerPhase
/ unitDivider
,
572 connectorMinimumPowerPerPhase
/ unitDivider
574 powerMeasurandValues
.L3
=
575 phase3FluctuatedValue
??
576 defaultFluctuatedPowerPerPhase
??
577 getRandomFloatRounded(
578 connectorMaximumPowerPerPhase
/ unitDivider
,
579 connectorMinimumPowerPerPhase
/ unitDivider
582 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
583 ? getRandomFloatFluctuatedRounded(
584 getLimitFromSampledValueTemplateCustomValue(
585 powerSampledValueTemplate
.value
,
586 connectorMaximumPower
/ unitDivider
,
587 connectorMinimumPower
/ unitDivider
,
590 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
591 fallbackValue
: connectorMinimumPower
/ unitDivider
594 powerSampledValueTemplate
.fluctuationPercent
??
595 Constants
.DEFAULT_FLUCTUATION_PERCENT
597 : getRandomFloatRounded(
598 connectorMaximumPower
/ unitDivider
,
599 connectorMinimumPower
/ unitDivider
601 powerMeasurandValues
.L2
= 0
602 powerMeasurandValues
.L3
= 0
604 powerMeasurandValues
.allPhases
= roundTo(
605 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
610 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
611 ? getRandomFloatFluctuatedRounded(
612 getLimitFromSampledValueTemplateCustomValue(
613 powerSampledValueTemplate
.value
,
614 connectorMaximumPower
/ unitDivider
,
615 connectorMinimumPower
/ unitDivider
,
618 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
619 fallbackValue
: connectorMinimumPower
/ unitDivider
622 powerSampledValueTemplate
.fluctuationPercent
??
623 Constants
.DEFAULT_FLUCTUATION_PERCENT
625 : getRandomFloatRounded(
626 connectorMaximumPower
/ unitDivider
,
627 connectorMinimumPower
/ unitDivider
631 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
632 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
634 meterValue
.sampledValue
.push(
635 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
637 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
638 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
639 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
641 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
642 connectorMaximumPowerRounded
||
643 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
644 connectorMinimumPowerRounded
||
648 `${chargingStation.logPrefix()} MeterValues measurand ${
649 meterValue.sampledValue[sampledValuesIndex].measurand ??
650 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
651 }: connector id ${connectorId}, transaction id ${
652 connector?.transactionId
653 }, value: ${connectorMinimumPowerRounded}/${
654 meterValue.sampledValue[sampledValuesIndex].value
655 }/${connectorMaximumPowerRounded}`
660 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
663 const phaseValue
= `L${phase}-N`
664 meterValue
.sampledValue
.push(
666 powerPerPhaseSampledValueTemplates
[
667 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
668 ] ?? powerSampledValueTemplate
,
669 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
671 phaseValue
as MeterValuePhase
674 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
675 const connectorMaximumPowerPerPhaseRounded
= roundTo(
676 connectorMaximumPowerPerPhase
/ unitDivider
,
679 const connectorMinimumPowerPerPhaseRounded
= roundTo(
680 connectorMinimumPowerPerPhase
/ unitDivider
,
684 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
685 connectorMaximumPowerPerPhaseRounded
||
686 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
687 connectorMinimumPowerPerPhaseRounded
||
691 `${chargingStation.logPrefix()} MeterValues measurand ${
692 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
693 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
695 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
696 }, connector id ${connectorId}, transaction id ${
697 connector?.transactionId
698 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
699 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
700 }/${connectorMaximumPowerPerPhaseRounded}`
705 // Current.Import measurand
706 currentSampledValueTemplate
= getSampledValueTemplate(
709 MeterValueMeasurand
.CURRENT_IMPORT
711 if (chargingStation
.getNumberOfPhases() === 3) {
712 currentPerPhaseSampledValueTemplates
= {
713 L1
: getSampledValueTemplate(
716 MeterValueMeasurand
.CURRENT_IMPORT
,
719 L2
: getSampledValueTemplate(
722 MeterValueMeasurand
.CURRENT_IMPORT
,
725 L3
: getSampledValueTemplate(
728 MeterValueMeasurand
.CURRENT_IMPORT
,
733 if (currentSampledValueTemplate
!= null) {
734 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
735 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
736 const errMsg
= `MeterValues measurand ${
737 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
738 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
739 chargingStation.templateFile
740 }, cannot calculate ${
741 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
743 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
744 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
745 const connectorMaximumAvailablePower
=
746 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
747 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
748 let connectorMaximumAmperage
: number
749 switch (chargingStation
.stationInfo
.currentOutType
) {
751 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
752 chargingStation
.getNumberOfPhases(),
753 connectorMaximumAvailablePower
,
754 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
755 chargingStation
.stationInfo
.voltageOut
!
757 if (chargingStation
.getNumberOfPhases() === 3) {
758 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
759 currentSampledValueTemplate
.value
761 ? getRandomFloatFluctuatedRounded(
762 getLimitFromSampledValueTemplateCustomValue(
763 currentSampledValueTemplate
.value
,
764 connectorMaximumAmperage
,
765 connectorMinimumAmperage
,
768 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
769 fallbackValue
: connectorMinimumAmperage
772 currentSampledValueTemplate
.fluctuationPercent
??
773 Constants
.DEFAULT_FLUCTUATION_PERCENT
776 const phase1FluctuatedValue
= isNotEmptyString(
777 currentPerPhaseSampledValueTemplates
.L1
?.value
779 ? getRandomFloatFluctuatedRounded(
780 getLimitFromSampledValueTemplateCustomValue(
781 currentPerPhaseSampledValueTemplates
.L1
.value
,
782 connectorMaximumAmperage
,
783 connectorMinimumAmperage
,
786 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
787 fallbackValue
: connectorMinimumAmperage
790 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
791 Constants
.DEFAULT_FLUCTUATION_PERCENT
794 const phase2FluctuatedValue
= isNotEmptyString(
795 currentPerPhaseSampledValueTemplates
.L2
?.value
797 ? getRandomFloatFluctuatedRounded(
798 getLimitFromSampledValueTemplateCustomValue(
799 currentPerPhaseSampledValueTemplates
.L2
.value
,
800 connectorMaximumAmperage
,
801 connectorMinimumAmperage
,
804 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
805 fallbackValue
: connectorMinimumAmperage
808 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
809 Constants
.DEFAULT_FLUCTUATION_PERCENT
812 const phase3FluctuatedValue
= isNotEmptyString(
813 currentPerPhaseSampledValueTemplates
.L3
?.value
815 ? getRandomFloatFluctuatedRounded(
816 getLimitFromSampledValueTemplateCustomValue(
817 currentPerPhaseSampledValueTemplates
.L3
.value
,
818 connectorMaximumAmperage
,
819 connectorMinimumAmperage
,
822 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
823 fallbackValue
: connectorMinimumAmperage
826 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
827 Constants
.DEFAULT_FLUCTUATION_PERCENT
830 currentMeasurandValues
.L1
=
831 phase1FluctuatedValue
??
832 defaultFluctuatedAmperagePerPhase
??
833 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
834 currentMeasurandValues
.L2
=
835 phase2FluctuatedValue
??
836 defaultFluctuatedAmperagePerPhase
??
837 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
838 currentMeasurandValues
.L3
=
839 phase3FluctuatedValue
??
840 defaultFluctuatedAmperagePerPhase
??
841 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
843 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
844 ? getRandomFloatFluctuatedRounded(
845 getLimitFromSampledValueTemplateCustomValue(
846 currentSampledValueTemplate
.value
,
847 connectorMaximumAmperage
,
848 connectorMinimumAmperage
,
851 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
852 fallbackValue
: connectorMinimumAmperage
855 currentSampledValueTemplate
.fluctuationPercent
??
856 Constants
.DEFAULT_FLUCTUATION_PERCENT
858 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
859 currentMeasurandValues
.L2
= 0
860 currentMeasurandValues
.L3
= 0
862 currentMeasurandValues
.allPhases
= roundTo(
863 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
864 chargingStation
.getNumberOfPhases(),
869 connectorMaximumAmperage
= DCElectricUtils
.amperage(
870 connectorMaximumAvailablePower
,
871 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
872 chargingStation
.stationInfo
.voltageOut
!
874 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
875 ? getRandomFloatFluctuatedRounded(
876 getLimitFromSampledValueTemplateCustomValue(
877 currentSampledValueTemplate
.value
,
878 connectorMaximumAmperage
,
879 connectorMinimumAmperage
,
882 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
883 fallbackValue
: connectorMinimumAmperage
886 currentSampledValueTemplate
.fluctuationPercent
??
887 Constants
.DEFAULT_FLUCTUATION_PERCENT
889 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
892 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
893 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
895 meterValue
.sampledValue
.push(
896 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
898 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
900 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
901 connectorMaximumAmperage
||
902 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
903 connectorMinimumAmperage
||
907 `${chargingStation.logPrefix()} MeterValues measurand ${
908 meterValue.sampledValue[sampledValuesIndex].measurand ??
909 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
910 }: connector id ${connectorId}, transaction id ${
911 connector?.transactionId
912 }, value: ${connectorMinimumAmperage}/${
913 meterValue.sampledValue[sampledValuesIndex].value
914 }/${connectorMaximumAmperage}`
919 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
922 const phaseValue
= `L${phase}`
923 meterValue
.sampledValue
.push(
925 currentPerPhaseSampledValueTemplates
[
926 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
927 ] ?? currentSampledValueTemplate
,
928 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
930 phaseValue
as MeterValuePhase
933 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
935 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
936 connectorMaximumAmperage
||
937 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
938 connectorMinimumAmperage
||
942 `${chargingStation.logPrefix()} MeterValues measurand ${
943 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
944 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
946 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
947 }, connector id ${connectorId}, transaction id ${
948 connector?.transactionId
949 }, value: ${connectorMinimumAmperage}/${
950 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
951 }/${connectorMaximumAmperage}`
956 // Energy.Active.Import.Register measurand (default)
957 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
958 if (energySampledValueTemplate
!= null) {
959 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
961 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
962 const connectorMaximumAvailablePower
=
963 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
964 const connectorMaximumEnergyRounded
= roundTo(
965 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
968 const connectorMinimumEnergyRounded
= roundTo(
969 energySampledValueTemplate
.minimumValue
?? 0,
972 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
973 ? getRandomFloatFluctuatedRounded(
974 getLimitFromSampledValueTemplateCustomValue(
975 energySampledValueTemplate
.value
,
976 connectorMaximumEnergyRounded
,
977 connectorMinimumEnergyRounded
,
979 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
980 fallbackValue
: connectorMinimumEnergyRounded
,
981 unitMultiplier
: unitDivider
984 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
986 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
987 // Persist previous value on connector
988 if (connector
!= null) {
990 connector
.energyActiveImportRegisterValue
!= null &&
991 connector
.energyActiveImportRegisterValue
>= 0 &&
992 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
993 connector
.transactionEnergyActiveImportRegisterValue
>= 0
995 connector
.energyActiveImportRegisterValue
+= energyValueRounded
996 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
998 connector
.energyActiveImportRegisterValue
= 0
999 connector
.transactionEnergyActiveImportRegisterValue
= 0
1002 meterValue
.sampledValue
.push(
1004 energySampledValueTemplate
,
1006 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
1012 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
1014 energyValueRounded
> connectorMaximumEnergyRounded
||
1015 energyValueRounded
< connectorMinimumEnergyRounded
||
1019 `${chargingStation.logPrefix()} MeterValues measurand ${
1020 meterValue.sampledValue[sampledValuesIndex].measurand ??
1021 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1022 }: connector id ${connectorId}, transaction id ${
1023 connector?.transactionId
1024 }, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1029 case OCPPVersion
.VERSION_20
:
1030 case OCPPVersion
.VERSION_201
:
1032 throw new BaseError(
1033 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1038 export const buildTransactionEndMeterValue
= (
1039 chargingStation
: ChargingStation
,
1040 connectorId
: number,
1041 meterStop
: number | undefined
1043 let meterValue
: MeterValue
1044 let sampledValueTemplate
: SampledValueTemplate
| undefined
1045 let unitDivider
: number
1046 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1047 case OCPPVersion
.VERSION_16
:
1049 timestamp
: new Date(),
1052 // Energy.Active.Import.Register measurand (default)
1053 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1054 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1055 meterValue
.sampledValue
.push(
1057 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1058 sampledValueTemplate
!,
1059 roundTo((meterStop
?? 0) / unitDivider
, 4),
1060 MeterValueContext
.TRANSACTION_END
1064 case OCPPVersion
.VERSION_20
:
1065 case OCPPVersion
.VERSION_201
:
1067 throw new BaseError(
1068 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1073 const checkMeasurandPowerDivider
= (
1074 chargingStation
: ChargingStation
,
1075 measurandType
: MeterValueMeasurand
| undefined
1077 if (chargingStation
.powerDivider
== null) {
1078 const errMsg
= `MeterValues measurand ${
1079 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1080 }: powerDivider is undefined`
1081 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1082 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1083 } else if (chargingStation
.powerDivider
<= 0) {
1084 const errMsg
= `MeterValues measurand ${
1085 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1086 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1087 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1088 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1092 const getLimitFromSampledValueTemplateCustomValue
= (
1093 value
: string | undefined,
1097 limitationEnabled
?: boolean
1098 fallbackValue
?: number
1099 unitMultiplier
?: number
1104 limitationEnabled
: false,
1110 const parsedValue
= Number.parseInt(value
?? '')
1111 if (options
.limitationEnabled
=== true) {
1114 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1115 (!isNaN(parsedValue
) ? parsedValue
: Number.POSITIVE_INFINITY
) * options
.unitMultiplier
!,
1121 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1122 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1125 const getSampledValueTemplate
= (
1126 chargingStation
: ChargingStation
,
1127 connectorId
: number,
1128 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1129 phase
?: MeterValuePhase
1130 ): SampledValueTemplate
| undefined => {
1131 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1132 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1134 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1139 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1140 getConfigurationKey(
1142 StandardParametersKey
.MeterValuesSampledData
1143 )?.value
?.includes(measurand
) === false
1146 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1147 StandardParametersKey.MeterValuesSampledData
1152 const sampledValueTemplates
=
1153 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1154 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1157 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1161 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1162 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1166 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1170 sampledValueTemplates
[index
]?.phase
=== phase
&&
1171 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1172 getConfigurationKey(
1174 StandardParametersKey
.MeterValuesSampledData
1175 )?.value
?.includes(measurand
) === true
1177 return sampledValueTemplates
[index
]
1180 sampledValueTemplates
[index
]?.phase
== null &&
1181 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1182 getConfigurationKey(
1184 StandardParametersKey
.MeterValuesSampledData
1185 )?.value
?.includes(measurand
) === true
1187 return sampledValueTemplates
[index
]
1189 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1190 (sampledValueTemplates
[index
]?.measurand
== null ||
1191 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1193 return sampledValueTemplates
[index
]
1196 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1197 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1198 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1199 throw new BaseError(errorMsg
)
1202 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1206 const buildSampledValue
= (
1207 sampledValueTemplate
: SampledValueTemplate
,
1209 context
?: MeterValueContext
,
1210 phase
?: MeterValuePhase
1211 ): SampledValue
=> {
1212 const sampledValueContext
= context
?? sampledValueTemplate
.context
1213 const sampledValueLocation
=
1214 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1215 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1216 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1218 ...(sampledValueTemplate
.unit
!= null && {
1219 unit
: sampledValueTemplate
.unit
1221 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1222 ...(sampledValueTemplate
.measurand
!= null && {
1223 measurand
: sampledValueTemplate
.measurand
1225 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1226 ...{ value
: value
.toString() },
1227 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1228 } satisfies SampledValue
1231 const getMeasurandDefaultLocation
= (
1232 measurandType
: MeterValueMeasurand
1233 ): MeterValueLocation
| undefined => {
1234 switch (measurandType
) {
1235 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1236 return MeterValueLocation
.EV
1240 // const getMeasurandDefaultUnit = (
1241 // measurandType: MeterValueMeasurand
1242 // ): MeterValueUnit | undefined => {
1243 // switch (measurandType) {
1244 // case MeterValueMeasurand.CURRENT_EXPORT:
1245 // case MeterValueMeasurand.CURRENT_IMPORT:
1246 // case MeterValueMeasurand.CURRENT_OFFERED:
1247 // return MeterValueUnit.AMP
1248 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1249 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1250 // return MeterValueUnit.WATT_HOUR
1251 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1252 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1253 // case MeterValueMeasurand.POWER_OFFERED:
1254 // return MeterValueUnit.WATT
1255 // case MeterValueMeasurand.STATE_OF_CHARGE:
1256 // return MeterValueUnit.PERCENT
1257 // case MeterValueMeasurand.VOLTAGE:
1258 // return MeterValueUnit.VOLT
1262 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1263 export class OCPPServiceUtils
{
1264 public static readonly getMessageTypeString
= getMessageTypeString
1265 public static readonly sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1266 public static readonly restoreConnectorStatus
= restoreConnectorStatus
1267 public static readonly isIdTagAuthorized
= isIdTagAuthorized
1268 public static readonly buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1269 protected static getSampledValueTemplate
= getSampledValueTemplate
1270 protected static buildSampledValue
= buildSampledValue
1272 protected constructor () {
1273 // This is intentional
1276 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | undefined | null): ErrorType
{
1277 if (isNotEmptyArray(errors
)) {
1278 for (const error
of errors
as DefinedError
[]) {
1279 switch (error
.keyword
) {
1281 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1282 case 'dependencies':
1284 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1287 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1291 return ErrorType
.FORMAT_VIOLATION
1294 public static isRequestCommandSupported (
1295 chargingStation
: ChargingStation
,
1296 command
: RequestCommand
1298 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1301 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1306 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1308 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1310 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1314 public static isIncomingRequestCommandSupported (
1315 chargingStation
: ChargingStation
,
1316 command
: IncomingRequestCommand
1318 const isIncomingRequestCommand
=
1319 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1321 isIncomingRequestCommand
&&
1322 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1326 isIncomingRequestCommand
&&
1327 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1329 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1331 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1335 public static isMessageTriggerSupported (
1336 chargingStation
: ChargingStation
,
1337 messageTrigger
: MessageTrigger
1339 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1340 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1344 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1346 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1349 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1354 public static isConnectorIdValid (
1355 chargingStation
: ChargingStation
,
1356 ocppCommand
: IncomingRequestCommand
,
1359 if (connectorId
< 0) {
1361 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1368 public static convertDateToISOString
<T
extends JsonType
>(object
: T
): void {
1369 for (const key
in object
) {
1370 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1371 if (isDate(object
![key
])) {
1372 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1373 (object
![key
] as string) = (object
![key
] as Date).toISOString()
1374 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
1375 } else if (typeof object
![key
] === 'object' && object
![key
] !== null) {
1376 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1377 OCPPServiceUtils
.convertDateToISOString
<T
>(object
![key
] as T
)
1382 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1383 if (chargingStation
.heartbeatSetInterval
== null) {
1384 chargingStation
.startHeartbeat()
1385 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1386 chargingStation
.restartHeartbeat()
1390 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1391 relativePath
: string,
1392 ocppVersion
: OCPPVersion
,
1393 moduleName
?: string,
1395 ): JSONSchemaType
<T
> {
1396 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1398 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1400 handleFileException(
1402 FileType
.JsonSchema
,
1403 error
as NodeJS
.ErrnoException
,
1404 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1405 { throwError
: false }
1407 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1408 return {} as JSONSchemaType
<T
>
1412 private static readonly logPrefix
= (
1413 ocppVersion
: OCPPVersion
,
1414 moduleName
?: string,
1418 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1419 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1420 : ` OCPP ${ocppVersion} |`
1421 return logPrefix(logMsg
)