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
,
23 type ConnectorStatusEnum
,
27 IncomingRequestCommand
,
29 type MeasurandPerPhaseSampledValueTemplates
,
39 type OCPP16ChargePointStatus
,
40 type OCPP16StatusNotificationRequest
,
41 type OCPP20ConnectorStatusEnumType
,
42 type OCPP20StatusNotificationRequest
,
46 type SampledValueTemplate
,
47 StandardParametersKey
,
48 type StatusNotificationRequest
,
49 type StatusNotificationResponse
50 } from
'../../types/index.js'
57 getRandomFloatFluctuatedRounded
,
58 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
:
93 status: status as OCPP16ChargePointStatus
,
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 as OCPP20ConnectorStatusEnumType
,
102 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
104 } satisfies OCPP20StatusNotificationRequest
106 throw new BaseError('Cannot build status notification payload: OCPP version not supported')
110 export const isIdTagAuthorized
= async (
111 chargingStation
: ChargingStation
,
114 ): Promise
<boolean> => {
116 !chargingStation
.getLocalAuthListEnabled() &&
117 chargingStation
.stationInfo
?.remoteAuthorization
=== false
120 `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
123 if (chargingStation
.getLocalAuthListEnabled() && isIdTagLocalAuthorized(chargingStation
, idTag
)) {
124 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
125 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!
126 connectorStatus
.localAuthorizeIdTag
= idTag
127 connectorStatus
.idTagLocalAuthorized
= true
129 } else if (chargingStation
.stationInfo
?.remoteAuthorization
=== true) {
130 return await isIdTagRemoteAuthorized(chargingStation
, connectorId
, idTag
)
135 const isIdTagLocalAuthorized
= (chargingStation
: ChargingStation
, idTag
: string): boolean => {
137 chargingStation
.hasIdTags() &&
139 chargingStation
.idTagsCache
140 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
141 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
!)!)
142 ?.find((tag
) => tag
=== idTag
)
147 const isIdTagRemoteAuthorized
= async (
148 chargingStation
: ChargingStation
,
151 ): Promise
<boolean> => {
152 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
153 chargingStation
.getConnectorStatus(connectorId
)!.authorizeIdTag
= idTag
156 await chargingStation
.ocppRequestService
.requestHandler
<AuthorizeRequest
, AuthorizeResponse
>(
158 RequestCommand
.AUTHORIZE
,
163 ).idTagInfo
.status === AuthorizationStatus
.ACCEPTED
167 export const sendAndSetConnectorStatus
= async (
168 chargingStation
: ChargingStation
,
170 status: ConnectorStatusEnum
,
172 options
?: { send
: boolean }
173 ): Promise
<void> => {
174 options
= { send
: true, ...options
}
176 checkConnectorStatusTransition(chargingStation
, connectorId
, status)
177 await chargingStation
.ocppRequestService
.requestHandler
<
178 StatusNotificationRequest
,
179 StatusNotificationResponse
182 RequestCommand
.STATUS_NOTIFICATION
,
183 buildStatusNotificationRequest(chargingStation
, connectorId
, status, evseId
)
186 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
187 chargingStation
.getConnectorStatus(connectorId
)!.status = status
188 chargingStation
.emit(ChargingStationEvents
.connectorStatusChanged
, {
190 ...chargingStation
.getConnectorStatus(connectorId
)
194 const checkConnectorStatusTransition
= (
195 chargingStation
: ChargingStation
,
197 status: ConnectorStatusEnum
199 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
200 const fromStatus
= chargingStation
.getConnectorStatus(connectorId
)!.status
201 let transitionAllowed
= false
202 switch (chargingStation
.stationInfo
?.ocppVersion
) {
203 case OCPPVersion
.VERSION_16
:
205 (connectorId
=== 0 &&
206 OCPP16Constants
.ChargePointStatusChargingStationTransitions
.findIndex(
207 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
210 OCPP16Constants
.ChargePointStatusConnectorTransitions
.findIndex(
211 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
214 transitionAllowed
= true
217 case OCPPVersion
.VERSION_20
:
218 case OCPPVersion
.VERSION_201
:
220 (connectorId
=== 0 &&
221 OCPP20Constants
.ChargingStationStatusTransitions
.findIndex(
222 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
225 OCPP20Constants
.ConnectorStatusTransitions
.findIndex(
226 (transition
) => transition
.from
=== fromStatus
&& transition
.to
=== status
229 transitionAllowed
= true
234 `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
237 if (!transitionAllowed
) {
239 `${chargingStation.logPrefix()} OCPP ${
240 chargingStation.stationInfo.ocppVersion
241 } connector id ${connectorId} status transition from '${
242 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
243 chargingStation.getConnectorStatus(connectorId)!.status
244 }' to '${status}' is not allowed`
247 return transitionAllowed
250 export const buildMeterValue
= (
251 chargingStation
: ChargingStation
,
253 transactionId
: number,
257 const connector
= chargingStation
.getConnectorStatus(connectorId
)
258 let meterValue
: MeterValue
259 let socSampledValueTemplate
: SampledValueTemplate
| undefined
260 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
261 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
262 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
263 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
264 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
265 let energySampledValueTemplate
: SampledValueTemplate
| undefined
266 switch (chargingStation
.stationInfo
?.ocppVersion
) {
267 case OCPPVersion
.VERSION_16
:
269 timestamp
: new Date(),
273 socSampledValueTemplate
= getSampledValueTemplate(
276 MeterValueMeasurand
.STATE_OF_CHARGE
278 if (socSampledValueTemplate
!= null) {
279 const socMaximumValue
= 100
280 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
281 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
282 ? getRandomFloatFluctuatedRounded(
283 parseInt(socSampledValueTemplate
.value
),
284 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
286 : getRandomInteger(socMaximumValue
, socMinimumValue
)
287 meterValue
.sampledValue
.push(
288 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
290 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
292 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
293 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
297 `${chargingStation.logPrefix()} MeterValues measurand ${
298 meterValue.sampledValue[sampledValuesIndex].measurand ??
299 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
300 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
301 meterValue.sampledValue[sampledValuesIndex].value
302 }/${socMaximumValue}`
307 voltageSampledValueTemplate
= getSampledValueTemplate(
310 MeterValueMeasurand
.VOLTAGE
312 if (voltageSampledValueTemplate
!= null) {
313 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
314 ? parseInt(voltageSampledValueTemplate
.value
)
315 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
316 chargingStation
.stationInfo
.voltageOut
!
317 const fluctuationPercent
=
318 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
319 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
320 voltageSampledValueTemplateValue
,
324 chargingStation
.getNumberOfPhases() !== 3 ||
325 (chargingStation
.getNumberOfPhases() === 3 &&
326 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
328 meterValue
.sampledValue
.push(
329 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
334 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
337 const phaseLineToNeutralValue
= `L${phase}-N`
338 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
341 MeterValueMeasurand
.VOLTAGE
,
342 phaseLineToNeutralValue
as MeterValuePhase
344 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
345 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
346 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
347 voltagePhaseLineToNeutralSampledValueTemplate
.value
349 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
350 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
351 chargingStation
.stationInfo
.voltageOut
!
352 const fluctuationPhaseToNeutralPercent
=
353 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
354 Constants
.DEFAULT_FLUCTUATION_PERCENT
355 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
356 voltagePhaseLineToNeutralSampledValueTemplateValue
,
357 fluctuationPhaseToNeutralPercent
360 meterValue
.sampledValue
.push(
362 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
363 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
365 phaseLineToNeutralValue
as MeterValuePhase
368 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
369 const phaseLineToLineValue
= `L${phase}-L${
370 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
371 ? (phase + 1) % chargingStation.getNumberOfPhases()
372 : chargingStation.getNumberOfPhases()
374 const voltagePhaseLineToLineValueRounded
= roundTo(
375 Math.sqrt(chargingStation
.getNumberOfPhases()) *
376 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
377 chargingStation
.stationInfo
.voltageOut
!,
380 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
383 MeterValueMeasurand
.VOLTAGE
,
384 phaseLineToLineValue
as MeterValuePhase
386 let voltagePhaseLineToLineMeasurandValue
: number | undefined
387 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
388 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
389 voltagePhaseLineToLineSampledValueTemplate
.value
391 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
392 : voltagePhaseLineToLineValueRounded
393 const fluctuationPhaseLineToLinePercent
=
394 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
395 Constants
.DEFAULT_FLUCTUATION_PERCENT
396 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
397 voltagePhaseLineToLineSampledValueTemplateValue
,
398 fluctuationPhaseLineToLinePercent
401 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
402 voltagePhaseLineToLineValueRounded
,
405 meterValue
.sampledValue
.push(
407 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
408 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
410 phaseLineToLineValue
as MeterValuePhase
416 // Power.Active.Import measurand
417 powerSampledValueTemplate
= getSampledValueTemplate(
420 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
422 if (chargingStation
.getNumberOfPhases() === 3) {
423 powerPerPhaseSampledValueTemplates
= {
424 L1
: getSampledValueTemplate(
427 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
430 L2
: getSampledValueTemplate(
433 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
436 L3
: getSampledValueTemplate(
439 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
444 if (powerSampledValueTemplate
!= null) {
445 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
446 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
447 const errMsg
= `MeterValues measurand ${
448 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
449 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
450 chargingStation.templateFile
451 }, cannot calculate ${
452 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
454 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
455 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
456 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
457 const connectorMaximumAvailablePower
=
458 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
459 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
460 const connectorMaximumPowerPerPhase
= Math.round(
461 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
463 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
464 const connectorMinimumPowerPerPhase
= Math.round(
465 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
467 switch (chargingStation
.stationInfo
.currentOutType
) {
469 if (chargingStation
.getNumberOfPhases() === 3) {
470 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
471 powerSampledValueTemplate
.value
473 ? getRandomFloatFluctuatedRounded(
474 getLimitFromSampledValueTemplateCustomValue(
475 powerSampledValueTemplate
.value
,
476 connectorMaximumPower
/ unitDivider
,
477 connectorMinimumPower
/ unitDivider
,
480 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
481 fallbackValue
: connectorMinimumPower
/ unitDivider
483 ) / chargingStation
.getNumberOfPhases(),
484 powerSampledValueTemplate
.fluctuationPercent
??
485 Constants
.DEFAULT_FLUCTUATION_PERCENT
488 const phase1FluctuatedValue
= isNotEmptyString(
489 powerPerPhaseSampledValueTemplates
.L1
?.value
491 ? getRandomFloatFluctuatedRounded(
492 getLimitFromSampledValueTemplateCustomValue(
493 powerPerPhaseSampledValueTemplates
.L1
?.value
,
494 connectorMaximumPowerPerPhase
/ unitDivider
,
495 connectorMinimumPowerPerPhase
/ unitDivider
,
498 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
499 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
502 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
503 Constants
.DEFAULT_FLUCTUATION_PERCENT
506 const phase2FluctuatedValue
= isNotEmptyString(
507 powerPerPhaseSampledValueTemplates
.L2
?.value
509 ? getRandomFloatFluctuatedRounded(
510 getLimitFromSampledValueTemplateCustomValue(
511 powerPerPhaseSampledValueTemplates
.L2
?.value
,
512 connectorMaximumPowerPerPhase
/ unitDivider
,
513 connectorMinimumPowerPerPhase
/ unitDivider
,
516 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
517 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
520 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
521 Constants
.DEFAULT_FLUCTUATION_PERCENT
524 const phase3FluctuatedValue
= isNotEmptyString(
525 powerPerPhaseSampledValueTemplates
.L3
?.value
527 ? getRandomFloatFluctuatedRounded(
528 getLimitFromSampledValueTemplateCustomValue(
529 powerPerPhaseSampledValueTemplates
.L3
?.value
,
530 connectorMaximumPowerPerPhase
/ unitDivider
,
531 connectorMinimumPowerPerPhase
/ unitDivider
,
534 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
535 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
538 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
539 Constants
.DEFAULT_FLUCTUATION_PERCENT
542 powerMeasurandValues
.L1
=
543 phase1FluctuatedValue
??
544 defaultFluctuatedPowerPerPhase
??
545 getRandomFloatRounded(
546 connectorMaximumPowerPerPhase
/ unitDivider
,
547 connectorMinimumPowerPerPhase
/ unitDivider
549 powerMeasurandValues
.L2
=
550 phase2FluctuatedValue
??
551 defaultFluctuatedPowerPerPhase
??
552 getRandomFloatRounded(
553 connectorMaximumPowerPerPhase
/ unitDivider
,
554 connectorMinimumPowerPerPhase
/ unitDivider
556 powerMeasurandValues
.L3
=
557 phase3FluctuatedValue
??
558 defaultFluctuatedPowerPerPhase
??
559 getRandomFloatRounded(
560 connectorMaximumPowerPerPhase
/ unitDivider
,
561 connectorMinimumPowerPerPhase
/ unitDivider
564 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
565 ? getRandomFloatFluctuatedRounded(
566 getLimitFromSampledValueTemplateCustomValue(
567 powerSampledValueTemplate
.value
,
568 connectorMaximumPower
/ unitDivider
,
569 connectorMinimumPower
/ unitDivider
,
572 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
573 fallbackValue
: connectorMinimumPower
/ unitDivider
576 powerSampledValueTemplate
.fluctuationPercent
??
577 Constants
.DEFAULT_FLUCTUATION_PERCENT
579 : getRandomFloatRounded(
580 connectorMaximumPower
/ unitDivider
,
581 connectorMinimumPower
/ unitDivider
583 powerMeasurandValues
.L2
= 0
584 powerMeasurandValues
.L3
= 0
586 powerMeasurandValues
.allPhases
= roundTo(
587 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
592 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
593 ? getRandomFloatFluctuatedRounded(
594 getLimitFromSampledValueTemplateCustomValue(
595 powerSampledValueTemplate
.value
,
596 connectorMaximumPower
/ unitDivider
,
597 connectorMinimumPower
/ unitDivider
,
600 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
601 fallbackValue
: connectorMinimumPower
/ unitDivider
604 powerSampledValueTemplate
.fluctuationPercent
??
605 Constants
.DEFAULT_FLUCTUATION_PERCENT
607 : getRandomFloatRounded(
608 connectorMaximumPower
/ unitDivider
,
609 connectorMinimumPower
/ unitDivider
613 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
614 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
616 meterValue
.sampledValue
.push(
617 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
619 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
620 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
621 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
623 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
624 connectorMaximumPowerRounded
||
625 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
626 connectorMinimumPowerRounded
||
630 `${chargingStation.logPrefix()} MeterValues measurand ${
631 meterValue.sampledValue[sampledValuesIndex].measurand ??
632 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
633 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
634 meterValue.sampledValue[sampledValuesIndex].value
635 }/${connectorMaximumPowerRounded}`
640 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
643 const phaseValue
= `L${phase}-N`
644 meterValue
.sampledValue
.push(
646 powerPerPhaseSampledValueTemplates
[
647 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
648 ] ?? powerSampledValueTemplate
,
649 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
651 phaseValue
as MeterValuePhase
654 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
655 const connectorMaximumPowerPerPhaseRounded
= roundTo(
656 connectorMaximumPowerPerPhase
/ unitDivider
,
659 const connectorMinimumPowerPerPhaseRounded
= roundTo(
660 connectorMinimumPowerPerPhase
/ unitDivider
,
664 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
665 connectorMaximumPowerPerPhaseRounded
||
666 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
667 connectorMinimumPowerPerPhaseRounded
||
671 `${chargingStation.logPrefix()} MeterValues measurand ${
672 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
673 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
675 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
676 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
677 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
678 }/${connectorMaximumPowerPerPhaseRounded}`
683 // Current.Import measurand
684 currentSampledValueTemplate
= getSampledValueTemplate(
687 MeterValueMeasurand
.CURRENT_IMPORT
689 if (chargingStation
.getNumberOfPhases() === 3) {
690 currentPerPhaseSampledValueTemplates
= {
691 L1
: getSampledValueTemplate(
694 MeterValueMeasurand
.CURRENT_IMPORT
,
697 L2
: getSampledValueTemplate(
700 MeterValueMeasurand
.CURRENT_IMPORT
,
703 L3
: getSampledValueTemplate(
706 MeterValueMeasurand
.CURRENT_IMPORT
,
711 if (currentSampledValueTemplate
!= null) {
712 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
713 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
714 const errMsg
= `MeterValues measurand ${
715 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
716 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
717 chargingStation.templateFile
718 }, cannot calculate ${
719 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
721 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
722 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
723 const connectorMaximumAvailablePower
=
724 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
725 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
726 let connectorMaximumAmperage
: number
727 switch (chargingStation
.stationInfo
.currentOutType
) {
729 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
730 chargingStation
.getNumberOfPhases(),
731 connectorMaximumAvailablePower
,
732 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
733 chargingStation
.stationInfo
.voltageOut
!
735 if (chargingStation
.getNumberOfPhases() === 3) {
736 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
737 currentSampledValueTemplate
.value
739 ? getRandomFloatFluctuatedRounded(
740 getLimitFromSampledValueTemplateCustomValue(
741 currentSampledValueTemplate
.value
,
742 connectorMaximumAmperage
,
743 connectorMinimumAmperage
,
746 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
747 fallbackValue
: connectorMinimumAmperage
750 currentSampledValueTemplate
.fluctuationPercent
??
751 Constants
.DEFAULT_FLUCTUATION_PERCENT
754 const phase1FluctuatedValue
= isNotEmptyString(
755 currentPerPhaseSampledValueTemplates
.L1
?.value
757 ? getRandomFloatFluctuatedRounded(
758 getLimitFromSampledValueTemplateCustomValue(
759 currentPerPhaseSampledValueTemplates
.L1
?.value
,
760 connectorMaximumAmperage
,
761 connectorMinimumAmperage
,
764 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
765 fallbackValue
: connectorMinimumAmperage
768 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
769 Constants
.DEFAULT_FLUCTUATION_PERCENT
772 const phase2FluctuatedValue
= isNotEmptyString(
773 currentPerPhaseSampledValueTemplates
.L2
?.value
775 ? getRandomFloatFluctuatedRounded(
776 getLimitFromSampledValueTemplateCustomValue(
777 currentPerPhaseSampledValueTemplates
.L2
?.value
,
778 connectorMaximumAmperage
,
779 connectorMinimumAmperage
,
782 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
783 fallbackValue
: connectorMinimumAmperage
786 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
787 Constants
.DEFAULT_FLUCTUATION_PERCENT
790 const phase3FluctuatedValue
= isNotEmptyString(
791 currentPerPhaseSampledValueTemplates
.L3
?.value
793 ? getRandomFloatFluctuatedRounded(
794 getLimitFromSampledValueTemplateCustomValue(
795 currentPerPhaseSampledValueTemplates
.L3
?.value
,
796 connectorMaximumAmperage
,
797 connectorMinimumAmperage
,
800 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
801 fallbackValue
: connectorMinimumAmperage
804 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
805 Constants
.DEFAULT_FLUCTUATION_PERCENT
808 currentMeasurandValues
.L1
=
809 phase1FluctuatedValue
??
810 defaultFluctuatedAmperagePerPhase
??
811 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
812 currentMeasurandValues
.L2
=
813 phase2FluctuatedValue
??
814 defaultFluctuatedAmperagePerPhase
??
815 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
816 currentMeasurandValues
.L3
=
817 phase3FluctuatedValue
??
818 defaultFluctuatedAmperagePerPhase
??
819 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
821 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
822 ? getRandomFloatFluctuatedRounded(
823 getLimitFromSampledValueTemplateCustomValue(
824 currentSampledValueTemplate
.value
,
825 connectorMaximumAmperage
,
826 connectorMinimumAmperage
,
829 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
830 fallbackValue
: connectorMinimumAmperage
833 currentSampledValueTemplate
.fluctuationPercent
??
834 Constants
.DEFAULT_FLUCTUATION_PERCENT
836 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
837 currentMeasurandValues
.L2
= 0
838 currentMeasurandValues
.L3
= 0
840 currentMeasurandValues
.allPhases
= roundTo(
841 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
842 chargingStation
.getNumberOfPhases(),
847 connectorMaximumAmperage
= DCElectricUtils
.amperage(
848 connectorMaximumAvailablePower
,
849 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
850 chargingStation
.stationInfo
.voltageOut
!
852 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
853 ? getRandomFloatFluctuatedRounded(
854 getLimitFromSampledValueTemplateCustomValue(
855 currentSampledValueTemplate
.value
,
856 connectorMaximumAmperage
,
857 connectorMinimumAmperage
,
860 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
861 fallbackValue
: connectorMinimumAmperage
864 currentSampledValueTemplate
.fluctuationPercent
??
865 Constants
.DEFAULT_FLUCTUATION_PERCENT
867 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
870 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
871 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
873 meterValue
.sampledValue
.push(
874 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
876 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
878 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
879 connectorMaximumAmperage
||
880 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
881 connectorMinimumAmperage
||
885 `${chargingStation.logPrefix()} MeterValues measurand ${
886 meterValue.sampledValue[sampledValuesIndex].measurand ??
887 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
888 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
889 meterValue.sampledValue[sampledValuesIndex].value
890 }/${connectorMaximumAmperage}`
895 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
898 const phaseValue
= `L${phase}`
899 meterValue
.sampledValue
.push(
901 currentPerPhaseSampledValueTemplates
[
902 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
903 ] ?? currentSampledValueTemplate
,
904 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
906 phaseValue
as MeterValuePhase
909 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
911 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
912 connectorMaximumAmperage
||
913 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
914 connectorMinimumAmperage
||
918 `${chargingStation.logPrefix()} MeterValues measurand ${
919 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
920 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
922 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
923 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
924 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
925 }/${connectorMaximumAmperage}`
930 // Energy.Active.Import.Register measurand (default)
931 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
932 if (energySampledValueTemplate
!= null) {
933 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
934 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
936 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
937 const connectorMaximumAvailablePower
=
938 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
939 const connectorMaximumEnergyRounded
= roundTo(
940 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
943 const connectorMinimumEnergyRounded
= roundTo(
944 energySampledValueTemplate
.minimumValue
?? 0,
947 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
948 ? getRandomFloatFluctuatedRounded(
949 getLimitFromSampledValueTemplateCustomValue(
950 energySampledValueTemplate
.value
,
951 connectorMaximumEnergyRounded
,
952 connectorMinimumEnergyRounded
,
954 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
955 fallbackValue
: connectorMinimumEnergyRounded
,
956 unitMultiplier
: unitDivider
959 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
961 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
962 // Persist previous value on connector
963 if (connector
!= null) {
965 connector
.energyActiveImportRegisterValue
!= null &&
966 connector
.energyActiveImportRegisterValue
>= 0 &&
967 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
968 connector
.transactionEnergyActiveImportRegisterValue
>= 0
970 connector
.energyActiveImportRegisterValue
+= energyValueRounded
971 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
973 connector
.energyActiveImportRegisterValue
= 0
974 connector
.transactionEnergyActiveImportRegisterValue
= 0
977 meterValue
.sampledValue
.push(
979 energySampledValueTemplate
,
981 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
987 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
989 energyValueRounded
> connectorMaximumEnergyRounded
||
990 energyValueRounded
< connectorMinimumEnergyRounded
||
994 `${chargingStation.logPrefix()} MeterValues measurand ${
995 meterValue.sampledValue[sampledValuesIndex].measurand ??
996 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
997 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1002 case OCPPVersion
.VERSION_20
:
1003 case OCPPVersion
.VERSION_201
:
1005 throw new BaseError(
1006 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1011 export const buildTransactionEndMeterValue
= (
1012 chargingStation
: ChargingStation
,
1013 connectorId
: number,
1014 meterStop
: number | undefined
1016 let meterValue
: MeterValue
1017 let sampledValueTemplate
: SampledValueTemplate
| undefined
1018 let unitDivider
: number
1019 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1020 case OCPPVersion
.VERSION_16
:
1022 timestamp
: new Date(),
1025 // Energy.Active.Import.Register measurand (default)
1026 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1027 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1028 meterValue
.sampledValue
.push(
1030 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1031 sampledValueTemplate
!,
1032 roundTo((meterStop
?? 0) / unitDivider
, 4),
1033 MeterValueContext
.TRANSACTION_END
1037 case OCPPVersion
.VERSION_20
:
1038 case OCPPVersion
.VERSION_201
:
1040 throw new BaseError(
1041 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1046 const checkMeasurandPowerDivider
= (
1047 chargingStation
: ChargingStation
,
1048 measurandType
: MeterValueMeasurand
| undefined
1050 if (chargingStation
.powerDivider
== null) {
1051 const errMsg
= `MeterValues measurand ${
1052 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1053 }: powerDivider is undefined`
1054 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1055 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1056 } else if (chargingStation
.powerDivider
<= 0) {
1057 const errMsg
= `MeterValues measurand ${
1058 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1059 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1060 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1061 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1065 const getLimitFromSampledValueTemplateCustomValue
= (
1066 value
: string | undefined,
1069 options
?: { limitationEnabled
?: boolean, fallbackValue
?: number, unitMultiplier
?: number }
1073 limitationEnabled
: false,
1079 const parsedValue
= parseInt(value
?? '')
1080 if (options
.limitationEnabled
=== true) {
1082 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1083 min((!isNaN(parsedValue
) ? parsedValue
: Infinity) * options
.unitMultiplier
!, maxLimit
),
1087 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1088 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1091 const getSampledValueTemplate
= (
1092 chargingStation
: ChargingStation
,
1093 connectorId
: number,
1094 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1095 phase
?: MeterValuePhase
1096 ): SampledValueTemplate
| undefined => {
1097 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1098 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1100 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1105 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1106 getConfigurationKey(
1108 StandardParametersKey
.MeterValuesSampledData
1109 )?.value
?.includes(measurand
) === false
1112 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1113 StandardParametersKey.MeterValuesSampledData
1118 const sampledValueTemplates
=
1119 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1120 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1123 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1127 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1128 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1132 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1136 sampledValueTemplates
[index
]?.phase
=== phase
&&
1137 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1138 getConfigurationKey(
1140 StandardParametersKey
.MeterValuesSampledData
1141 )?.value
?.includes(measurand
) === true
1143 return sampledValueTemplates
[index
]
1146 sampledValueTemplates
[index
]?.phase
== null &&
1147 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1148 getConfigurationKey(
1150 StandardParametersKey
.MeterValuesSampledData
1151 )?.value
?.includes(measurand
) === true
1153 return sampledValueTemplates
[index
]
1155 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1156 (sampledValueTemplates
[index
]?.measurand
== null ||
1157 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1159 return sampledValueTemplates
[index
]
1162 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1163 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1164 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1165 throw new BaseError(errorMsg
)
1168 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1172 const buildSampledValue
= (
1173 sampledValueTemplate
: SampledValueTemplate
,
1175 context
?: MeterValueContext
,
1176 phase
?: MeterValuePhase
1177 ): SampledValue
=> {
1178 const sampledValueContext
= context
?? sampledValueTemplate
.context
1179 const sampledValueLocation
=
1180 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1181 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1182 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1184 ...(sampledValueTemplate
.unit
!= null && {
1185 unit
: sampledValueTemplate
.unit
1187 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1188 ...(sampledValueTemplate
.measurand
!= null && {
1189 measurand
: sampledValueTemplate
.measurand
1191 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1192 ...{ value
: value
.toString() },
1193 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1194 } satisfies SampledValue
1197 const getMeasurandDefaultLocation
= (
1198 measurandType
: MeterValueMeasurand
1199 ): MeterValueLocation
| undefined => {
1200 switch (measurandType
) {
1201 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1202 return MeterValueLocation
.EV
1206 // const getMeasurandDefaultUnit = (
1207 // measurandType: MeterValueMeasurand
1208 // ): MeterValueUnit | undefined => {
1209 // switch (measurandType) {
1210 // case MeterValueMeasurand.CURRENT_EXPORT:
1211 // case MeterValueMeasurand.CURRENT_IMPORT:
1212 // case MeterValueMeasurand.CURRENT_OFFERED:
1213 // return MeterValueUnit.AMP
1214 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1215 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1216 // return MeterValueUnit.WATT_HOUR
1217 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1218 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1219 // case MeterValueMeasurand.POWER_OFFERED:
1220 // return MeterValueUnit.WATT
1221 // case MeterValueMeasurand.STATE_OF_CHARGE:
1222 // return MeterValueUnit.PERCENT
1223 // case MeterValueMeasurand.VOLTAGE:
1224 // return MeterValueUnit.VOLT
1228 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1229 export class OCPPServiceUtils
{
1230 public static getMessageTypeString
= getMessageTypeString
1231 public static sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1232 public static isIdTagAuthorized
= isIdTagAuthorized
1233 public static buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1234 protected static getSampledValueTemplate
= getSampledValueTemplate
1235 protected static buildSampledValue
= buildSampledValue
1237 protected constructor () {
1238 // This is intentional
1241 public static ajvErrorsToErrorType (errors
: ErrorObject
[] | null | undefined): ErrorType
{
1242 if (isNotEmptyArray(errors
)) {
1243 for (const error
of errors
as DefinedError
[]) {
1244 switch (error
.keyword
) {
1246 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
1247 case 'dependencies':
1249 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
1252 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
1256 return ErrorType
.FORMAT_VIOLATION
1259 public static isRequestCommandSupported (
1260 chargingStation
: ChargingStation
,
1261 command
: RequestCommand
1263 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1266 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1271 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1273 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1275 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1279 public static isIncomingRequestCommandSupported (
1280 chargingStation
: ChargingStation
,
1281 command
: IncomingRequestCommand
1283 const isIncomingRequestCommand
=
1284 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1286 isIncomingRequestCommand
&&
1287 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1291 isIncomingRequestCommand
&&
1292 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1294 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1296 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1300 public static isMessageTriggerSupported (
1301 chargingStation
: ChargingStation
,
1302 messageTrigger
: MessageTrigger
1304 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1305 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1309 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1311 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1314 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1319 public static isConnectorIdValid (
1320 chargingStation
: ChargingStation
,
1321 ocppCommand
: IncomingRequestCommand
,
1324 if (connectorId
< 0) {
1326 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1333 public static convertDateToISOString
<T
extends JsonType
>(obj
: T
): void {
1334 for (const key
in obj
) {
1335 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1336 if (isDate(obj
![key
])) {
1337 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1338 (obj
![key
] as string) = (obj
![key
] as Date).toISOString()
1339 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
1340 } else if (typeof obj
![key
] === 'object' && obj
![key
] !== null) {
1341 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
1342 OCPPServiceUtils
.convertDateToISOString
<T
>(obj
![key
] as T
)
1347 public static startHeartbeatInterval (chargingStation
: ChargingStation
, interval
: number): void {
1348 if (chargingStation
.heartbeatSetInterval
== null) {
1349 chargingStation
.startHeartbeat()
1350 } else if (chargingStation
.getHeartbeatInterval() !== interval
) {
1351 chargingStation
.restartHeartbeat()
1355 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1356 relativePath
: string,
1357 ocppVersion
: OCPPVersion
,
1358 moduleName
?: string,
1360 ): JSONSchemaType
<T
> {
1361 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1363 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1365 handleFileException(
1367 FileType
.JsonSchema
,
1368 error
as NodeJS
.ErrnoException
,
1369 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1370 { throwError
: false }
1372 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1373 return {} as JSONSchemaType
<T
>
1377 private static readonly logPrefix
= (
1378 ocppVersion
: OCPPVersion
,
1379 moduleName
?: string,
1383 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1384 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1385 : ` OCPP ${ocppVersion} |`
1386 return logPrefix(logMsg
)