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> => {
203 if (connectorStatus
?.reservation
!= null) {
204 await sendAndSetConnectorStatus(chargingStation
, connectorId
, ConnectorStatusEnum
.Reserved
)
205 } else if (connectorStatus
?.status !== ConnectorStatusEnum
.Available
) {
206 await sendAndSetConnectorStatus(chargingStation
, connectorId
, ConnectorStatusEnum
.Available
)
210 const checkConnectorStatusTransition
= (
211 chargingStation
: ChargingStation
,
213 status: ConnectorStatusEnum
215 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)?.status
216 let transitionAllowed
= false
217 switch (chargingStation
.stationInfo
?.ocppVersion
) {
218 case OCPPVersion
.VERSION_16
:
220 (connectorId
=== 0 &&
221 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
222 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
225 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
226 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
229 transitionAllowed
= true
232 case OCPPVersion
.VERSION_20
:
233 case OCPPVersion
.VERSION_201
:
235 (connectorId
=== 0 &&
236 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
237 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
240 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
241 transition
=> transition
.from
=== fromStatus
&& transition
.to
=== status
244 transitionAllowed
= true
249 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
252 if (!transitionAllowed
) {
254 `${chargingStation.logPrefix()} OCPP ${
255 chargingStation.stationInfo.ocppVersion
256 } connector id ${connectorId} status transition from '${
257 chargingStation.getConnectorStatus(connectorId)?.status
258 }' to '${status}' is not allowed`
261 return transitionAllowed
264 export const buildMeterValue
= (
265 chargingStation
: ChargingStation
,
267 transactionId
: number,
271 const connector
= chargingStation
.getConnectorStatus(connectorId
)
272 let meterValue
: MeterValue
273 let socSampledValueTemplate
: SampledValueTemplate
| undefined
274 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
275 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
276 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
277 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
278 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
279 let energySampledValueTemplate
: SampledValueTemplate
| undefined
280 switch (chargingStation
.stationInfo
?.ocppVersion
) {
281 case OCPPVersion
.VERSION_16
:
283 timestamp
: new Date(),
287 socSampledValueTemplate
= getSampledValueTemplate(
290 MeterValueMeasurand
.STATE_OF_CHARGE
292 if (socSampledValueTemplate
!= null) {
293 const socMaximumValue
= 100
294 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
295 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
296 ? getRandomFloatFluctuatedRounded(
297 parseInt(socSampledValueTemplate
.value
),
298 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
300 : randomInt(socMinimumValue
, socMaximumValue
)
301 meterValue
.sampledValue
.push(
302 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
304 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
306 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
307 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
311 `${chargingStation.logPrefix()} MeterValues measurand ${
312 meterValue.sampledValue[sampledValuesIndex].measurand ??
313 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
314 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
315 meterValue.sampledValue[sampledValuesIndex].value
316 }/${socMaximumValue}`
321 voltageSampledValueTemplate
= getSampledValueTemplate(
324 MeterValueMeasurand
.VOLTAGE
326 if (voltageSampledValueTemplate
!= null) {
327 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
328 ? parseInt(voltageSampledValueTemplate
.value
)
329 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
330 chargingStation
.stationInfo
.voltageOut
!
331 const fluctuationPercent
=
332 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
333 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
334 voltageSampledValueTemplateValue
,
338 chargingStation
.getNumberOfPhases() !== 3 ||
339 (chargingStation
.getNumberOfPhases() === 3 &&
340 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
342 meterValue
.sampledValue
.push(
343 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
348 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
351 const phaseLineToNeutralValue
= `L${phase}-N`
352 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
355 MeterValueMeasurand
.VOLTAGE
,
356 phaseLineToNeutralValue
as MeterValuePhase
358 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
359 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
360 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
361 voltagePhaseLineToNeutralSampledValueTemplate
.value
363 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
364 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
365 chargingStation
.stationInfo
.voltageOut
!
366 const fluctuationPhaseToNeutralPercent
=
367 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
368 Constants
.DEFAULT_FLUCTUATION_PERCENT
369 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
370 voltagePhaseLineToNeutralSampledValueTemplateValue
,
371 fluctuationPhaseToNeutralPercent
374 meterValue
.sampledValue
.push(
376 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
377 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
379 phaseLineToNeutralValue
as MeterValuePhase
382 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
383 const phaseLineToLineValue
= `L${phase}-L${
384 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
385 ? (phase + 1) % chargingStation.getNumberOfPhases()
386 : chargingStation.getNumberOfPhases()
388 const voltagePhaseLineToLineValueRounded
= roundTo(
389 Math.sqrt(chargingStation
.getNumberOfPhases()) *
390 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
391 chargingStation
.stationInfo
.voltageOut
!,
394 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
397 MeterValueMeasurand
.VOLTAGE
,
398 phaseLineToLineValue
as MeterValuePhase
400 let voltagePhaseLineToLineMeasurandValue
: number | undefined
401 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
402 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
403 voltagePhaseLineToLineSampledValueTemplate
.value
405 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
406 : voltagePhaseLineToLineValueRounded
407 const fluctuationPhaseLineToLinePercent
=
408 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
409 Constants
.DEFAULT_FLUCTUATION_PERCENT
410 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
411 voltagePhaseLineToLineSampledValueTemplateValue
,
412 fluctuationPhaseLineToLinePercent
415 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
416 voltagePhaseLineToLineValueRounded
,
419 meterValue
.sampledValue
.push(
421 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
422 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
424 phaseLineToLineValue
as MeterValuePhase
430 // Power.Active.Import measurand
431 powerSampledValueTemplate
= getSampledValueTemplate(
434 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
436 if (chargingStation
.getNumberOfPhases() === 3) {
437 powerPerPhaseSampledValueTemplates
= {
438 L1
: getSampledValueTemplate(
441 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
444 L2
: getSampledValueTemplate(
447 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
450 L3
: getSampledValueTemplate(
453 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
458 if (powerSampledValueTemplate
!= null) {
459 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
460 const errMsg
= `MeterValues measurand ${
461 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
462 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
463 chargingStation.templateFile
464 }, cannot calculate ${
465 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
467 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
468 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
469 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
470 const connectorMaximumAvailablePower
=
471 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
472 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
473 const connectorMaximumPowerPerPhase
= Math.round(
474 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
476 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
477 const connectorMinimumPowerPerPhase
= Math.round(
478 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
480 switch (chargingStation
.stationInfo
.currentOutType
) {
482 if (chargingStation
.getNumberOfPhases() === 3) {
483 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
484 powerSampledValueTemplate
.value
486 ? getRandomFloatFluctuatedRounded(
487 getLimitFromSampledValueTemplateCustomValue(
488 powerSampledValueTemplate
.value
,
489 connectorMaximumPower
/ unitDivider
,
490 connectorMinimumPower
/ unitDivider
,
493 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
494 fallbackValue
: connectorMinimumPower
/ unitDivider
496 ) / chargingStation
.getNumberOfPhases(),
497 powerSampledValueTemplate
.fluctuationPercent
??
498 Constants
.DEFAULT_FLUCTUATION_PERCENT
501 const phase1FluctuatedValue
= isNotEmptyString(
502 powerPerPhaseSampledValueTemplates
.L1
?.value
504 ? getRandomFloatFluctuatedRounded(
505 getLimitFromSampledValueTemplateCustomValue(
506 powerPerPhaseSampledValueTemplates
.L1
.value
,
507 connectorMaximumPowerPerPhase
/ unitDivider
,
508 connectorMinimumPowerPerPhase
/ unitDivider
,
511 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
512 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
515 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
516 Constants
.DEFAULT_FLUCTUATION_PERCENT
519 const phase2FluctuatedValue
= isNotEmptyString(
520 powerPerPhaseSampledValueTemplates
.L2
?.value
522 ? getRandomFloatFluctuatedRounded(
523 getLimitFromSampledValueTemplateCustomValue(
524 powerPerPhaseSampledValueTemplates
.L2
.value
,
525 connectorMaximumPowerPerPhase
/ unitDivider
,
526 connectorMinimumPowerPerPhase
/ unitDivider
,
529 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
530 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
533 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
534 Constants
.DEFAULT_FLUCTUATION_PERCENT
537 const phase3FluctuatedValue
= isNotEmptyString(
538 powerPerPhaseSampledValueTemplates
.L3
?.value
540 ? getRandomFloatFluctuatedRounded(
541 getLimitFromSampledValueTemplateCustomValue(
542 powerPerPhaseSampledValueTemplates
.L3
.value
,
543 connectorMaximumPowerPerPhase
/ unitDivider
,
544 connectorMinimumPowerPerPhase
/ unitDivider
,
547 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
548 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
551 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
552 Constants
.DEFAULT_FLUCTUATION_PERCENT
555 powerMeasurandValues
.L1
=
556 phase1FluctuatedValue
??
557 defaultFluctuatedPowerPerPhase
??
558 getRandomFloatRounded(
559 connectorMaximumPowerPerPhase
/ unitDivider
,
560 connectorMinimumPowerPerPhase
/ unitDivider
562 powerMeasurandValues
.L2
=
563 phase2FluctuatedValue
??
564 defaultFluctuatedPowerPerPhase
??
565 getRandomFloatRounded(
566 connectorMaximumPowerPerPhase
/ unitDivider
,
567 connectorMinimumPowerPerPhase
/ unitDivider
569 powerMeasurandValues
.L3
=
570 phase3FluctuatedValue
??
571 defaultFluctuatedPowerPerPhase
??
572 getRandomFloatRounded(
573 connectorMaximumPowerPerPhase
/ unitDivider
,
574 connectorMinimumPowerPerPhase
/ unitDivider
577 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
578 ? getRandomFloatFluctuatedRounded(
579 getLimitFromSampledValueTemplateCustomValue(
580 powerSampledValueTemplate
.value
,
581 connectorMaximumPower
/ unitDivider
,
582 connectorMinimumPower
/ unitDivider
,
585 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
586 fallbackValue
: connectorMinimumPower
/ unitDivider
589 powerSampledValueTemplate
.fluctuationPercent
??
590 Constants
.DEFAULT_FLUCTUATION_PERCENT
592 : getRandomFloatRounded(
593 connectorMaximumPower
/ unitDivider
,
594 connectorMinimumPower
/ unitDivider
596 powerMeasurandValues
.L2
= 0
597 powerMeasurandValues
.L3
= 0
599 powerMeasurandValues
.allPhases
= roundTo(
600 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
605 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
606 ? getRandomFloatFluctuatedRounded(
607 getLimitFromSampledValueTemplateCustomValue(
608 powerSampledValueTemplate
.value
,
609 connectorMaximumPower
/ unitDivider
,
610 connectorMinimumPower
/ unitDivider
,
613 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
614 fallbackValue
: connectorMinimumPower
/ unitDivider
617 powerSampledValueTemplate
.fluctuationPercent
??
618 Constants
.DEFAULT_FLUCTUATION_PERCENT
620 : getRandomFloatRounded(
621 connectorMaximumPower
/ unitDivider
,
622 connectorMinimumPower
/ unitDivider
626 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
627 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
629 meterValue
.sampledValue
.push(
630 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
632 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
633 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
634 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
636 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
637 connectorMaximumPowerRounded
||
638 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
639 connectorMinimumPowerRounded
||
643 `${chargingStation.logPrefix()} MeterValues measurand ${
644 meterValue.sampledValue[sampledValuesIndex].measurand ??
645 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
646 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
647 meterValue.sampledValue[sampledValuesIndex].value
648 }/${connectorMaximumPowerRounded}`
653 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
656 const phaseValue
= `L${phase}-N`
657 meterValue
.sampledValue
.push(
659 powerPerPhaseSampledValueTemplates
[
660 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
661 ] ?? powerSampledValueTemplate
,
662 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
664 phaseValue
as MeterValuePhase
667 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
668 const connectorMaximumPowerPerPhaseRounded
= roundTo(
669 connectorMaximumPowerPerPhase
/ unitDivider
,
672 const connectorMinimumPowerPerPhaseRounded
= roundTo(
673 connectorMinimumPowerPerPhase
/ unitDivider
,
677 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
678 connectorMaximumPowerPerPhaseRounded
||
679 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
680 connectorMinimumPowerPerPhaseRounded
||
684 `${chargingStation.logPrefix()} MeterValues measurand ${
685 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
686 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
688 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
689 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
690 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
691 }/${connectorMaximumPowerPerPhaseRounded}`
696 // Current.Import measurand
697 currentSampledValueTemplate
= getSampledValueTemplate(
700 MeterValueMeasurand
.CURRENT_IMPORT
702 if (chargingStation
.getNumberOfPhases() === 3) {
703 currentPerPhaseSampledValueTemplates
= {
704 L1
: getSampledValueTemplate(
707 MeterValueMeasurand
.CURRENT_IMPORT
,
710 L2
: getSampledValueTemplate(
713 MeterValueMeasurand
.CURRENT_IMPORT
,
716 L3
: getSampledValueTemplate(
719 MeterValueMeasurand
.CURRENT_IMPORT
,
724 if (currentSampledValueTemplate
!= null) {
725 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
726 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
727 const errMsg
= `MeterValues measurand ${
728 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
729 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
730 chargingStation.templateFile
731 }, cannot calculate ${
732 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
734 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
735 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
736 const connectorMaximumAvailablePower
=
737 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
738 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
739 let connectorMaximumAmperage
: number
740 switch (chargingStation
.stationInfo
.currentOutType
) {
742 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
743 chargingStation
.getNumberOfPhases(),
744 connectorMaximumAvailablePower
,
745 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
746 chargingStation
.stationInfo
.voltageOut
!
748 if (chargingStation
.getNumberOfPhases() === 3) {
749 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
750 currentSampledValueTemplate
.value
752 ? getRandomFloatFluctuatedRounded(
753 getLimitFromSampledValueTemplateCustomValue(
754 currentSampledValueTemplate
.value
,
755 connectorMaximumAmperage
,
756 connectorMinimumAmperage
,
759 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
760 fallbackValue
: connectorMinimumAmperage
763 currentSampledValueTemplate
.fluctuationPercent
??
764 Constants
.DEFAULT_FLUCTUATION_PERCENT
767 const phase1FluctuatedValue
= isNotEmptyString(
768 currentPerPhaseSampledValueTemplates
.L1
?.value
770 ? getRandomFloatFluctuatedRounded(
771 getLimitFromSampledValueTemplateCustomValue(
772 currentPerPhaseSampledValueTemplates
.L1
.value
,
773 connectorMaximumAmperage
,
774 connectorMinimumAmperage
,
777 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
778 fallbackValue
: connectorMinimumAmperage
781 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
782 Constants
.DEFAULT_FLUCTUATION_PERCENT
785 const phase2FluctuatedValue
= isNotEmptyString(
786 currentPerPhaseSampledValueTemplates
.L2
?.value
788 ? getRandomFloatFluctuatedRounded(
789 getLimitFromSampledValueTemplateCustomValue(
790 currentPerPhaseSampledValueTemplates
.L2
.value
,
791 connectorMaximumAmperage
,
792 connectorMinimumAmperage
,
795 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
796 fallbackValue
: connectorMinimumAmperage
799 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
800 Constants
.DEFAULT_FLUCTUATION_PERCENT
803 const phase3FluctuatedValue
= isNotEmptyString(
804 currentPerPhaseSampledValueTemplates
.L3
?.value
806 ? getRandomFloatFluctuatedRounded(
807 getLimitFromSampledValueTemplateCustomValue(
808 currentPerPhaseSampledValueTemplates
.L3
.value
,
809 connectorMaximumAmperage
,
810 connectorMinimumAmperage
,
813 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
814 fallbackValue
: connectorMinimumAmperage
817 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
818 Constants
.DEFAULT_FLUCTUATION_PERCENT
821 currentMeasurandValues
.L1
=
822 phase1FluctuatedValue
??
823 defaultFluctuatedAmperagePerPhase
??
824 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
825 currentMeasurandValues
.L2
=
826 phase2FluctuatedValue
??
827 defaultFluctuatedAmperagePerPhase
??
828 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
829 currentMeasurandValues
.L3
=
830 phase3FluctuatedValue
??
831 defaultFluctuatedAmperagePerPhase
??
832 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
834 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
835 ? getRandomFloatFluctuatedRounded(
836 getLimitFromSampledValueTemplateCustomValue(
837 currentSampledValueTemplate
.value
,
838 connectorMaximumAmperage
,
839 connectorMinimumAmperage
,
842 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
843 fallbackValue
: connectorMinimumAmperage
846 currentSampledValueTemplate
.fluctuationPercent
??
847 Constants
.DEFAULT_FLUCTUATION_PERCENT
849 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
850 currentMeasurandValues
.L2
= 0
851 currentMeasurandValues
.L3
= 0
853 currentMeasurandValues
.allPhases
= roundTo(
854 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
855 chargingStation
.getNumberOfPhases(),
860 connectorMaximumAmperage
= DCElectricUtils
.amperage(
861 connectorMaximumAvailablePower
,
862 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
863 chargingStation
.stationInfo
.voltageOut
!
865 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
866 ? getRandomFloatFluctuatedRounded(
867 getLimitFromSampledValueTemplateCustomValue(
868 currentSampledValueTemplate
.value
,
869 connectorMaximumAmperage
,
870 connectorMinimumAmperage
,
873 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
874 fallbackValue
: connectorMinimumAmperage
877 currentSampledValueTemplate
.fluctuationPercent
??
878 Constants
.DEFAULT_FLUCTUATION_PERCENT
880 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
883 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
884 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
886 meterValue
.sampledValue
.push(
887 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
889 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
891 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
892 connectorMaximumAmperage
||
893 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
894 connectorMinimumAmperage
||
898 `${chargingStation.logPrefix()} MeterValues measurand ${
899 meterValue.sampledValue[sampledValuesIndex].measurand ??
900 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
901 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
902 meterValue.sampledValue[sampledValuesIndex].value
903 }/${connectorMaximumAmperage}`
908 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
911 const phaseValue
= `L${phase}`
912 meterValue
.sampledValue
.push(
914 currentPerPhaseSampledValueTemplates
[
915 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
916 ] ?? currentSampledValueTemplate
,
917 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
919 phaseValue
as MeterValuePhase
922 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
924 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
925 connectorMaximumAmperage
||
926 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
927 connectorMinimumAmperage
||
931 `${chargingStation.logPrefix()} MeterValues measurand ${
932 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
933 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
935 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
936 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
937 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
938 }/${connectorMaximumAmperage}`
943 // Energy.Active.Import.Register measurand (default)
944 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
945 if (energySampledValueTemplate
!= null) {
946 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
948 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
949 const connectorMaximumAvailablePower
=
950 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
951 const connectorMaximumEnergyRounded
= roundTo(
952 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
955 const connectorMinimumEnergyRounded
= roundTo(
956 energySampledValueTemplate
.minimumValue
?? 0,
959 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
960 ? getRandomFloatFluctuatedRounded(
961 getLimitFromSampledValueTemplateCustomValue(
962 energySampledValueTemplate
.value
,
963 connectorMaximumEnergyRounded
,
964 connectorMinimumEnergyRounded
,
966 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
967 fallbackValue
: connectorMinimumEnergyRounded
,
968 unitMultiplier
: unitDivider
971 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
973 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
974 // Persist previous value on connector
975 if (connector
!= null) {
977 connector
.energyActiveImportRegisterValue
!= null &&
978 connector
.energyActiveImportRegisterValue
>= 0 &&
979 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
980 connector
.transactionEnergyActiveImportRegisterValue
>= 0
982 connector
.energyActiveImportRegisterValue
+= energyValueRounded
983 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
985 connector
.energyActiveImportRegisterValue
= 0
986 connector
.transactionEnergyActiveImportRegisterValue
= 0
989 meterValue
.sampledValue
.push(
991 energySampledValueTemplate
,
993 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
999 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
1001 energyValueRounded
> connectorMaximumEnergyRounded
||
1002 energyValueRounded
< connectorMinimumEnergyRounded
||
1006 `${chargingStation.logPrefix()} MeterValues measurand ${
1007 meterValue.sampledValue[sampledValuesIndex].measurand ??
1008 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1009 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1014 case OCPPVersion
.VERSION_20
:
1015 case OCPPVersion
.VERSION_201
:
1017 throw new BaseError(
1018 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1023 export const buildTransactionEndMeterValue
= (
1024 chargingStation
: ChargingStation
,
1025 connectorId
: number,
1026 meterStop
: number | undefined
1028 let meterValue
: MeterValue
1029 let sampledValueTemplate
: SampledValueTemplate
| undefined
1030 let unitDivider
: number
1031 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1032 case OCPPVersion
.VERSION_16
:
1034 timestamp
: new Date(),
1037 // Energy.Active.Import.Register measurand (default)
1038 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1039 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1040 meterValue
.sampledValue
.push(
1042 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1043 sampledValueTemplate
!,
1044 roundTo((meterStop
?? 0) / unitDivider
, 4),
1045 MeterValueContext
.TRANSACTION_END
1049 case OCPPVersion
.VERSION_20
:
1050 case OCPPVersion
.VERSION_201
:
1052 throw new BaseError(
1053 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1058 const checkMeasurandPowerDivider
= (
1059 chargingStation
: ChargingStation
,
1060 measurandType
: MeterValueMeasurand
| undefined
1062 if (chargingStation
.powerDivider
== null) {
1063 const errMsg
= `MeterValues measurand ${
1064 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1065 }: powerDivider is undefined`
1066 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1067 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1068 } else if (chargingStation
.powerDivider
<= 0) {
1069 const errMsg
= `MeterValues measurand ${
1070 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1071 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1072 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1073 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1077 const getLimitFromSampledValueTemplateCustomValue
= (
1078 value
: string | undefined,
1081 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1085 limitationEnabled
: false,
1091 const parsedValue
= parseInt(value
?? '')
1092 if (options
.limitationEnabled
=== true) {
1094 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1095 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1099 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1100 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1103 const getSampledValueTemplate
= (
1104 chargingStation
: ChargingStation
,
1105 connectorId
: number,
1106 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1107 phase
?: MeterValuePhase
1108 ): SampledValueTemplate
| undefined => {
1109 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1110 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1112 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1117 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1118 getConfigurationKey(
1120 StandardParametersKey
.MeterValuesSampledData
1121 )?.value
?.includes(measurand
) === false
1124 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1125 StandardParametersKey.MeterValuesSampledData
1130 const sampledValueTemplates
=
1131 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1132 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1135 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1139 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1140 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1144 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1148 sampledValueTemplates
[index
]?.phase
=== phase
&&
1149 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1150 getConfigurationKey(
1152 StandardParametersKey
.MeterValuesSampledData
1153 )?.value
?.includes(measurand
) === true
1155 return sampledValueTemplates
[index
]
1158 sampledValueTemplates
[index
]?.phase
== null &&
1159 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1160 getConfigurationKey(
1162 StandardParametersKey
.MeterValuesSampledData
1163 )?.value
?.includes(measurand
) === true
1165 return sampledValueTemplates
[index
]
1167 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1168 (sampledValueTemplates
[index
]?.measurand
== null ||
1169 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1171 return sampledValueTemplates
[index
]
1174 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1175 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1176 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1177 throw new BaseError(errorMsg
)
1180 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1184 const buildSampledValue
= (
1185 sampledValueTemplate
: SampledValueTemplate
,
1187 context
?: MeterValueContext
,
1188 phase
?: MeterValuePhase
1189 ): SampledValue
=> {
1190 const sampledValueContext
= context
?? sampledValueTemplate
.context
1191 const sampledValueLocation
=
1192 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1193 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1194 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1196 ...(sampledValueTemplate
.unit
!= null && {
1197 unit
: sampledValueTemplate
.unit
1199 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1200 ...(sampledValueTemplate
.measurand
!= null && {
1201 measurand
: sampledValueTemplate
.measurand
1203 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1204 ...{ value
: value
.toString() },
1205 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1206 } satisfies SampledValue
1209 const getMeasurandDefaultLocation
= (
1210 measurandType
: MeterValueMeasurand
1211 ): MeterValueLocation
| undefined => {
1212 switch (measurandType
) {
1213 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1214 return MeterValueLocation
.EV
1218 // const getMeasurandDefaultUnit = (
1219 // measurandType: MeterValueMeasurand
1220 // ): MeterValueUnit | undefined => {
1221 // switch (measurandType) {
1222 // case MeterValueMeasurand.CURRENT_EXPORT:
1223 // case MeterValueMeasurand.CURRENT_IMPORT:
1224 // case MeterValueMeasurand.CURRENT_OFFERED:
1225 // return MeterValueUnit.AMP
1226 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1227 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1228 // return MeterValueUnit.WATT_HOUR
1229 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1230 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1231 // case MeterValueMeasurand.POWER_OFFERED:
1232 // return MeterValueUnit.WATT
1233 // case MeterValueMeasurand.STATE_OF_CHARGE:
1234 // return MeterValueUnit.PERCENT
1235 // case MeterValueMeasurand.VOLTAGE:
1236 // return MeterValueUnit.VOLT
1240 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1241 export class OCPPServiceUtils
{
1242 public static readonly getMessageTypeString
= getMessageTypeString
1243 public static readonly sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1244 public static readonly restoreConnectorStatus
= restoreConnectorStatus
1245 public static readonly isIdTagAuthorized
= isIdTagAuthorized
1246 public static readonly buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1247 protected static getSampledValueTemplate
= getSampledValueTemplate
1248 protected static buildSampledValue
= buildSampledValue
1250 protected constructor () {
1251 // This is intentional
1254 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | undefined | null): ErrorType
{
1255 if (isNotEmptyArray(errors
)) {
1256 for (const error
of errors
as DefinedError
[]) {
1257 switch (error
.keyword
) {
1259 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1260 case 'dependencies':
1262 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1265 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1269 return ErrorType
.FORMAT_VIOLATION
1272 public static isRequestCommandSupported (
1273 chargingStation
: ChargingStation
,
1274 command
: RequestCommand
1276 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1279 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1284 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1286 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1288 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1292 public static isIncomingRequestCommandSupported (
1293 chargingStation
: ChargingStation
,
1294 command
: IncomingRequestCommand
1296 const isIncomingRequestCommand
=
1297 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1299 isIncomingRequestCommand
&&
1300 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1304 isIncomingRequestCommand
&&
1305 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1307 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1309 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1313 public static isMessageTriggerSupported (
1314 chargingStation
: ChargingStation
,
1315 messageTrigger
: MessageTrigger
1317 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1318 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1322 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1324 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1327 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1332 public static isConnectorIdValid (
1333 chargingStation
: ChargingStation
,
1334 ocppCommand
: IncomingRequestCommand
,
1337 if (connectorId
< 0) {
1339 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1346 public static convertDateToISOString
<T
extends JsonType
>(object
: T
): void {
1347 for (const key
in object
) {
1348 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1349 if (isDate(object
![key
])) {
1350 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1351 (object
![key
] as string) = (object
![key
] as Date).toISOString()
1352 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
1353 } else if (typeof object
![key
] === 'object' && object
![key
] !== null) {
1354 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1355 OCPPServiceUtils
.convertDateToISOString
<T
>(object
![key
] as T
)
1360 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1361 if (chargingStation
.heartbeatSetInterval
== null) {
1362 chargingStation
.startHeartbeat()
1363 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1364 chargingStation
.restartHeartbeat()
1368 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1369 relativePath
: string,
1370 ocppVersion
: OCPPVersion
,
1371 moduleName
?: string,
1373 ): JSONSchemaType
<T
> {
1374 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1376 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1378 handleFileException(
1380 FileType
.JsonSchema
,
1381 error
as NodeJS
.ErrnoException
,
1382 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1383 { throwError
: false }
1385 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1386 return {} as JSONSchemaType
<T
>
1390 private static readonly logPrefix
= (
1391 ocppVersion
: OCPPVersion
,
1392 moduleName
?: string,
1396 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1397 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1398 : ` OCPP ${ocppVersion} |`
1399 return logPrefix(logMsg
)