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 ajvErrorsToErrorType
= (errors
: ErrorObject
[] | undefined | null): ErrorType
=> {
268 if (isNotEmptyArray(errors
)) {
269 for (const error
of errors
as DefinedError
[]) {
270 switch (error
.keyword
) {
272 return ErrorType
.TYPE_CONSTRAINT_VIOLATION
275 return ErrorType
.OCCURRENCE_CONSTRAINT_VIOLATION
278 return ErrorType
.PROPERTY_CONSTRAINT_VIOLATION
282 return ErrorType
.FORMAT_VIOLATION
285 export const convertDateToISOString
= <T
extends JsonType
>(object
: T
): void => {
286 for (const key
in object
) {
287 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
288 if (isDate(object
![key
])) {
289 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
290 (object
![key
] as string) = (object
![key
] as Date).toISOString()
291 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
292 } else if (typeof object
![key
] === 'object' && object
![key
] !== null) {
293 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
294 convertDateToISOString
<T
>(object
![key
] as T
)
299 export const buildMeterValue
= (
300 chargingStation
: ChargingStation
,
302 transactionId
: number,
306 const connector
= chargingStation
.getConnectorStatus(connectorId
)
307 let meterValue
: MeterValue
308 let socSampledValueTemplate
: SampledValueTemplate
| undefined
309 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
310 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
311 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
312 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
313 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
314 let energySampledValueTemplate
: SampledValueTemplate
| undefined
315 switch (chargingStation
.stationInfo
?.ocppVersion
) {
316 case OCPPVersion
.VERSION_16
:
318 timestamp
: new Date(),
322 socSampledValueTemplate
= getSampledValueTemplate(
325 MeterValueMeasurand
.STATE_OF_CHARGE
327 if (socSampledValueTemplate
!= null) {
328 const socMaximumValue
= 100
329 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
330 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
331 ? getRandomFloatFluctuatedRounded(
332 Number.parseInt(socSampledValueTemplate
.value
),
333 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
335 : randomInt(socMinimumValue
, socMaximumValue
)
336 meterValue
.sampledValue
.push(
337 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
339 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
341 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
342 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
346 `${chargingStation.logPrefix()} MeterValues measurand ${
347 meterValue.sampledValue[sampledValuesIndex].measurand ??
348 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
349 }: connector id ${connectorId}, transaction id ${
350 connector?.transactionId
351 }, value: ${socMinimumValue}/${
352 meterValue.sampledValue[sampledValuesIndex].value
353 }/${socMaximumValue}`
358 voltageSampledValueTemplate
= getSampledValueTemplate(
361 MeterValueMeasurand
.VOLTAGE
363 if (voltageSampledValueTemplate
!= null) {
364 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
365 ? Number.parseInt(voltageSampledValueTemplate
.value
)
366 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
367 chargingStation
.stationInfo
.voltageOut
!
368 const fluctuationPercent
=
369 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
370 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
371 voltageSampledValueTemplateValue
,
375 chargingStation
.getNumberOfPhases() !== 3 ||
376 (chargingStation
.getNumberOfPhases() === 3 &&
377 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
379 meterValue
.sampledValue
.push(
380 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
385 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
388 const phaseLineToNeutralValue
= `L${phase}-N`
389 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
392 MeterValueMeasurand
.VOLTAGE
,
393 phaseLineToNeutralValue
as MeterValuePhase
395 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
396 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
397 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
398 voltagePhaseLineToNeutralSampledValueTemplate
.value
400 ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
401 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
402 chargingStation
.stationInfo
.voltageOut
!
403 const fluctuationPhaseToNeutralPercent
=
404 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
405 Constants
.DEFAULT_FLUCTUATION_PERCENT
406 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
407 voltagePhaseLineToNeutralSampledValueTemplateValue
,
408 fluctuationPhaseToNeutralPercent
411 meterValue
.sampledValue
.push(
413 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
414 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
416 phaseLineToNeutralValue
as MeterValuePhase
419 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
420 const phaseLineToLineValue
= `L${phase}-L${
421 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
422 ? (phase + 1) % chargingStation.getNumberOfPhases()
423 : chargingStation.getNumberOfPhases()
425 const voltagePhaseLineToLineValueRounded
= roundTo(
426 Math.sqrt(chargingStation
.getNumberOfPhases()) *
427 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
428 chargingStation
.stationInfo
.voltageOut
!,
431 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
434 MeterValueMeasurand
.VOLTAGE
,
435 phaseLineToLineValue
as MeterValuePhase
437 let voltagePhaseLineToLineMeasurandValue
: number | undefined
438 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
439 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
440 voltagePhaseLineToLineSampledValueTemplate
.value
442 ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
443 : voltagePhaseLineToLineValueRounded
444 const fluctuationPhaseLineToLinePercent
=
445 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
446 Constants
.DEFAULT_FLUCTUATION_PERCENT
447 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
448 voltagePhaseLineToLineSampledValueTemplateValue
,
449 fluctuationPhaseLineToLinePercent
452 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
453 voltagePhaseLineToLineValueRounded
,
456 meterValue
.sampledValue
.push(
458 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
459 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
461 phaseLineToLineValue
as MeterValuePhase
467 // Power.Active.Import measurand
468 powerSampledValueTemplate
= getSampledValueTemplate(
471 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
473 if (chargingStation
.getNumberOfPhases() === 3) {
474 powerPerPhaseSampledValueTemplates
= {
475 L1
: getSampledValueTemplate(
478 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
481 L2
: getSampledValueTemplate(
484 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
487 L3
: getSampledValueTemplate(
490 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
495 if (powerSampledValueTemplate
!= null) {
496 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
497 const errMsg
= `MeterValues measurand ${
498 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
499 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
500 chargingStation.templateFile
501 }, cannot calculate ${
502 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
504 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
505 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
506 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
507 const connectorMaximumAvailablePower
=
508 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
509 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
510 const connectorMaximumPowerPerPhase
= Math.round(
511 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
513 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
514 const connectorMinimumPowerPerPhase
= Math.round(
515 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
517 switch (chargingStation
.stationInfo
.currentOutType
) {
519 if (chargingStation
.getNumberOfPhases() === 3) {
520 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
521 powerSampledValueTemplate
.value
523 ? getRandomFloatFluctuatedRounded(
524 getLimitFromSampledValueTemplateCustomValue(
525 powerSampledValueTemplate
.value
,
526 connectorMaximumPower
/ unitDivider
,
527 connectorMinimumPower
/ unitDivider
,
530 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
531 fallbackValue
: connectorMinimumPower
/ unitDivider
533 ) / chargingStation
.getNumberOfPhases(),
534 powerSampledValueTemplate
.fluctuationPercent
??
535 Constants
.DEFAULT_FLUCTUATION_PERCENT
538 const phase1FluctuatedValue
= isNotEmptyString(
539 powerPerPhaseSampledValueTemplates
.L1
?.value
541 ? getRandomFloatFluctuatedRounded(
542 getLimitFromSampledValueTemplateCustomValue(
543 powerPerPhaseSampledValueTemplates
.L1
.value
,
544 connectorMaximumPowerPerPhase
/ unitDivider
,
545 connectorMinimumPowerPerPhase
/ unitDivider
,
548 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
549 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
552 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
553 Constants
.DEFAULT_FLUCTUATION_PERCENT
556 const phase2FluctuatedValue
= isNotEmptyString(
557 powerPerPhaseSampledValueTemplates
.L2
?.value
559 ? getRandomFloatFluctuatedRounded(
560 getLimitFromSampledValueTemplateCustomValue(
561 powerPerPhaseSampledValueTemplates
.L2
.value
,
562 connectorMaximumPowerPerPhase
/ unitDivider
,
563 connectorMinimumPowerPerPhase
/ unitDivider
,
566 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
567 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
570 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
571 Constants
.DEFAULT_FLUCTUATION_PERCENT
574 const phase3FluctuatedValue
= isNotEmptyString(
575 powerPerPhaseSampledValueTemplates
.L3
?.value
577 ? getRandomFloatFluctuatedRounded(
578 getLimitFromSampledValueTemplateCustomValue(
579 powerPerPhaseSampledValueTemplates
.L3
.value
,
580 connectorMaximumPowerPerPhase
/ unitDivider
,
581 connectorMinimumPowerPerPhase
/ unitDivider
,
584 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
585 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
588 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
589 Constants
.DEFAULT_FLUCTUATION_PERCENT
592 powerMeasurandValues
.L1
=
593 phase1FluctuatedValue
??
594 defaultFluctuatedPowerPerPhase
??
595 getRandomFloatRounded(
596 connectorMaximumPowerPerPhase
/ unitDivider
,
597 connectorMinimumPowerPerPhase
/ unitDivider
599 powerMeasurandValues
.L2
=
600 phase2FluctuatedValue
??
601 defaultFluctuatedPowerPerPhase
??
602 getRandomFloatRounded(
603 connectorMaximumPowerPerPhase
/ unitDivider
,
604 connectorMinimumPowerPerPhase
/ unitDivider
606 powerMeasurandValues
.L3
=
607 phase3FluctuatedValue
??
608 defaultFluctuatedPowerPerPhase
??
609 getRandomFloatRounded(
610 connectorMaximumPowerPerPhase
/ unitDivider
,
611 connectorMinimumPowerPerPhase
/ unitDivider
614 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
615 ? getRandomFloatFluctuatedRounded(
616 getLimitFromSampledValueTemplateCustomValue(
617 powerSampledValueTemplate
.value
,
618 connectorMaximumPower
/ unitDivider
,
619 connectorMinimumPower
/ unitDivider
,
622 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
623 fallbackValue
: connectorMinimumPower
/ unitDivider
626 powerSampledValueTemplate
.fluctuationPercent
??
627 Constants
.DEFAULT_FLUCTUATION_PERCENT
629 : getRandomFloatRounded(
630 connectorMaximumPower
/ unitDivider
,
631 connectorMinimumPower
/ unitDivider
633 powerMeasurandValues
.L2
= 0
634 powerMeasurandValues
.L3
= 0
636 powerMeasurandValues
.allPhases
= roundTo(
637 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
642 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
643 ? getRandomFloatFluctuatedRounded(
644 getLimitFromSampledValueTemplateCustomValue(
645 powerSampledValueTemplate
.value
,
646 connectorMaximumPower
/ unitDivider
,
647 connectorMinimumPower
/ unitDivider
,
650 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
651 fallbackValue
: connectorMinimumPower
/ unitDivider
654 powerSampledValueTemplate
.fluctuationPercent
??
655 Constants
.DEFAULT_FLUCTUATION_PERCENT
657 : getRandomFloatRounded(
658 connectorMaximumPower
/ unitDivider
,
659 connectorMinimumPower
/ unitDivider
663 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
664 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
666 meterValue
.sampledValue
.push(
667 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
669 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
670 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
671 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
673 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
674 connectorMaximumPowerRounded
||
675 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
676 connectorMinimumPowerRounded
||
680 `${chargingStation.logPrefix()} MeterValues measurand ${
681 meterValue.sampledValue[sampledValuesIndex].measurand ??
682 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
683 }: connector id ${connectorId}, transaction id ${
684 connector?.transactionId
685 }, value: ${connectorMinimumPowerRounded}/${
686 meterValue.sampledValue[sampledValuesIndex].value
687 }/${connectorMaximumPowerRounded}`
692 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
695 const phaseValue
= `L${phase}-N`
696 meterValue
.sampledValue
.push(
698 powerPerPhaseSampledValueTemplates
[
699 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
700 ] ?? powerSampledValueTemplate
,
701 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
703 phaseValue
as MeterValuePhase
706 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
707 const connectorMaximumPowerPerPhaseRounded
= roundTo(
708 connectorMaximumPowerPerPhase
/ unitDivider
,
711 const connectorMinimumPowerPerPhaseRounded
= roundTo(
712 connectorMinimumPowerPerPhase
/ unitDivider
,
716 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
717 connectorMaximumPowerPerPhaseRounded
||
718 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
719 connectorMinimumPowerPerPhaseRounded
||
723 `${chargingStation.logPrefix()} MeterValues measurand ${
724 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
725 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
727 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
728 }, connector id ${connectorId}, transaction id ${
729 connector?.transactionId
730 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
731 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
732 }/${connectorMaximumPowerPerPhaseRounded}`
737 // Current.Import measurand
738 currentSampledValueTemplate
= getSampledValueTemplate(
741 MeterValueMeasurand
.CURRENT_IMPORT
743 if (chargingStation
.getNumberOfPhases() === 3) {
744 currentPerPhaseSampledValueTemplates
= {
745 L1
: getSampledValueTemplate(
748 MeterValueMeasurand
.CURRENT_IMPORT
,
751 L2
: getSampledValueTemplate(
754 MeterValueMeasurand
.CURRENT_IMPORT
,
757 L3
: getSampledValueTemplate(
760 MeterValueMeasurand
.CURRENT_IMPORT
,
765 if (currentSampledValueTemplate
!= null) {
766 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
767 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
768 const errMsg
= `MeterValues measurand ${
769 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
770 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
771 chargingStation.templateFile
772 }, cannot calculate ${
773 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
775 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
776 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
777 const connectorMaximumAvailablePower
=
778 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
779 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
780 let connectorMaximumAmperage
: number
781 switch (chargingStation
.stationInfo
.currentOutType
) {
783 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
784 chargingStation
.getNumberOfPhases(),
785 connectorMaximumAvailablePower
,
786 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
787 chargingStation
.stationInfo
.voltageOut
!
789 if (chargingStation
.getNumberOfPhases() === 3) {
790 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
791 currentSampledValueTemplate
.value
793 ? getRandomFloatFluctuatedRounded(
794 getLimitFromSampledValueTemplateCustomValue(
795 currentSampledValueTemplate
.value
,
796 connectorMaximumAmperage
,
797 connectorMinimumAmperage
,
800 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
801 fallbackValue
: connectorMinimumAmperage
804 currentSampledValueTemplate
.fluctuationPercent
??
805 Constants
.DEFAULT_FLUCTUATION_PERCENT
808 const phase1FluctuatedValue
= isNotEmptyString(
809 currentPerPhaseSampledValueTemplates
.L1
?.value
811 ? getRandomFloatFluctuatedRounded(
812 getLimitFromSampledValueTemplateCustomValue(
813 currentPerPhaseSampledValueTemplates
.L1
.value
,
814 connectorMaximumAmperage
,
815 connectorMinimumAmperage
,
818 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
819 fallbackValue
: connectorMinimumAmperage
822 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
823 Constants
.DEFAULT_FLUCTUATION_PERCENT
826 const phase2FluctuatedValue
= isNotEmptyString(
827 currentPerPhaseSampledValueTemplates
.L2
?.value
829 ? getRandomFloatFluctuatedRounded(
830 getLimitFromSampledValueTemplateCustomValue(
831 currentPerPhaseSampledValueTemplates
.L2
.value
,
832 connectorMaximumAmperage
,
833 connectorMinimumAmperage
,
836 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
837 fallbackValue
: connectorMinimumAmperage
840 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
841 Constants
.DEFAULT_FLUCTUATION_PERCENT
844 const phase3FluctuatedValue
= isNotEmptyString(
845 currentPerPhaseSampledValueTemplates
.L3
?.value
847 ? getRandomFloatFluctuatedRounded(
848 getLimitFromSampledValueTemplateCustomValue(
849 currentPerPhaseSampledValueTemplates
.L3
.value
,
850 connectorMaximumAmperage
,
851 connectorMinimumAmperage
,
854 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
855 fallbackValue
: connectorMinimumAmperage
858 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
859 Constants
.DEFAULT_FLUCTUATION_PERCENT
862 currentMeasurandValues
.L1
=
863 phase1FluctuatedValue
??
864 defaultFluctuatedAmperagePerPhase
??
865 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
866 currentMeasurandValues
.L2
=
867 phase2FluctuatedValue
??
868 defaultFluctuatedAmperagePerPhase
??
869 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
870 currentMeasurandValues
.L3
=
871 phase3FluctuatedValue
??
872 defaultFluctuatedAmperagePerPhase
??
873 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
875 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
876 ? getRandomFloatFluctuatedRounded(
877 getLimitFromSampledValueTemplateCustomValue(
878 currentSampledValueTemplate
.value
,
879 connectorMaximumAmperage
,
880 connectorMinimumAmperage
,
883 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
884 fallbackValue
: connectorMinimumAmperage
887 currentSampledValueTemplate
.fluctuationPercent
??
888 Constants
.DEFAULT_FLUCTUATION_PERCENT
890 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
891 currentMeasurandValues
.L2
= 0
892 currentMeasurandValues
.L3
= 0
894 currentMeasurandValues
.allPhases
= roundTo(
895 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
896 chargingStation
.getNumberOfPhases(),
901 connectorMaximumAmperage
= DCElectricUtils
.amperage(
902 connectorMaximumAvailablePower
,
903 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
904 chargingStation
.stationInfo
.voltageOut
!
906 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
907 ? getRandomFloatFluctuatedRounded(
908 getLimitFromSampledValueTemplateCustomValue(
909 currentSampledValueTemplate
.value
,
910 connectorMaximumAmperage
,
911 connectorMinimumAmperage
,
914 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
915 fallbackValue
: connectorMinimumAmperage
918 currentSampledValueTemplate
.fluctuationPercent
??
919 Constants
.DEFAULT_FLUCTUATION_PERCENT
921 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
924 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
925 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
927 meterValue
.sampledValue
.push(
928 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
930 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
932 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
933 connectorMaximumAmperage
||
934 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
935 connectorMinimumAmperage
||
939 `${chargingStation.logPrefix()} MeterValues measurand ${
940 meterValue.sampledValue[sampledValuesIndex].measurand ??
941 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
942 }: connector id ${connectorId}, transaction id ${
943 connector?.transactionId
944 }, value: ${connectorMinimumAmperage}/${
945 meterValue.sampledValue[sampledValuesIndex].value
946 }/${connectorMaximumAmperage}`
951 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
954 const phaseValue
= `L${phase}`
955 meterValue
.sampledValue
.push(
957 currentPerPhaseSampledValueTemplates
[
958 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
959 ] ?? currentSampledValueTemplate
,
960 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
962 phaseValue
as MeterValuePhase
965 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
967 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
968 connectorMaximumAmperage
||
969 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
970 connectorMinimumAmperage
||
974 `${chargingStation.logPrefix()} MeterValues measurand ${
975 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
976 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
978 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
979 }, connector id ${connectorId}, transaction id ${
980 connector?.transactionId
981 }, value: ${connectorMinimumAmperage}/${
982 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
983 }/${connectorMaximumAmperage}`
988 // Energy.Active.Import.Register measurand (default)
989 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
990 if (energySampledValueTemplate
!= null) {
991 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
993 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
994 const connectorMaximumAvailablePower
=
995 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
996 const connectorMaximumEnergyRounded
= roundTo(
997 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
1000 const connectorMinimumEnergyRounded
= roundTo(
1001 energySampledValueTemplate
.minimumValue
?? 0,
1004 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
1005 ? getRandomFloatFluctuatedRounded(
1006 getLimitFromSampledValueTemplateCustomValue(
1007 energySampledValueTemplate
.value
,
1008 connectorMaximumEnergyRounded
,
1009 connectorMinimumEnergyRounded
,
1011 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
1012 fallbackValue
: connectorMinimumEnergyRounded
,
1013 unitMultiplier
: unitDivider
1016 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
1018 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
1019 // Persist previous value on connector
1020 if (connector
!= null) {
1022 connector
.energyActiveImportRegisterValue
!= null &&
1023 connector
.energyActiveImportRegisterValue
>= 0 &&
1024 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
1025 connector
.transactionEnergyActiveImportRegisterValue
>= 0
1027 connector
.energyActiveImportRegisterValue
+= energyValueRounded
1028 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
1030 connector
.energyActiveImportRegisterValue
= 0
1031 connector
.transactionEnergyActiveImportRegisterValue
= 0
1034 meterValue
.sampledValue
.push(
1036 energySampledValueTemplate
,
1038 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
1044 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
1046 energyValueRounded
> connectorMaximumEnergyRounded
||
1047 energyValueRounded
< connectorMinimumEnergyRounded
||
1051 `${chargingStation.logPrefix()} MeterValues measurand ${
1052 meterValue.sampledValue[sampledValuesIndex].measurand ??
1053 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1054 }: connector id ${connectorId}, transaction id ${
1055 connector?.transactionId
1056 }, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1061 case OCPPVersion
.VERSION_20
:
1062 case OCPPVersion
.VERSION_201
:
1064 throw new BaseError(
1065 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1070 export const buildTransactionEndMeterValue
= (
1071 chargingStation
: ChargingStation
,
1072 connectorId
: number,
1073 meterStop
: number | undefined
1075 let meterValue
: MeterValue
1076 let sampledValueTemplate
: SampledValueTemplate
| undefined
1077 let unitDivider
: number
1078 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1079 case OCPPVersion
.VERSION_16
:
1081 timestamp
: new Date(),
1084 // Energy.Active.Import.Register measurand (default)
1085 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1086 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1087 meterValue
.sampledValue
.push(
1089 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1090 sampledValueTemplate
!,
1091 roundTo((meterStop
?? 0) / unitDivider
, 4),
1092 MeterValueContext
.TRANSACTION_END
1096 case OCPPVersion
.VERSION_20
:
1097 case OCPPVersion
.VERSION_201
:
1099 throw new BaseError(
1100 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1105 const checkMeasurandPowerDivider
= (
1106 chargingStation
: ChargingStation
,
1107 measurandType
: MeterValueMeasurand
| undefined
1109 if (chargingStation
.powerDivider
== null) {
1110 const errMsg
= `MeterValues measurand ${
1111 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1112 }: powerDivider is undefined`
1113 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1114 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1115 } else if (chargingStation
.powerDivider
<= 0) {
1116 const errMsg
= `MeterValues measurand ${
1117 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1118 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1119 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1120 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1124 const getLimitFromSampledValueTemplateCustomValue
= (
1125 value
: string | undefined,
1129 limitationEnabled
?: boolean
1130 fallbackValue
?: number
1131 unitMultiplier
?: number
1136 limitationEnabled
: false,
1142 const parsedValue
= Number.parseInt(value
?? '')
1143 if (options
.limitationEnabled
=== true) {
1146 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1147 (!isNaN(parsedValue
) ? parsedValue
: Number.POSITIVE_INFINITY
) * options
.unitMultiplier
!,
1153 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1154 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1157 const getSampledValueTemplate
= (
1158 chargingStation
: ChargingStation
,
1159 connectorId
: number,
1160 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1161 phase
?: MeterValuePhase
1162 ): SampledValueTemplate
| undefined => {
1163 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1164 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1166 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1171 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1172 getConfigurationKey(
1174 StandardParametersKey
.MeterValuesSampledData
1175 )?.value
?.includes(measurand
) === false
1178 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1179 StandardParametersKey.MeterValuesSampledData
1184 const sampledValueTemplates
=
1185 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1186 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1189 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1193 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1194 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1198 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1202 sampledValueTemplates
[index
]?.phase
=== phase
&&
1203 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1204 getConfigurationKey(
1206 StandardParametersKey
.MeterValuesSampledData
1207 )?.value
?.includes(measurand
) === true
1209 return sampledValueTemplates
[index
]
1212 sampledValueTemplates
[index
]?.phase
== null &&
1213 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1214 getConfigurationKey(
1216 StandardParametersKey
.MeterValuesSampledData
1217 )?.value
?.includes(measurand
) === true
1219 return sampledValueTemplates
[index
]
1221 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1222 (sampledValueTemplates
[index
]?.measurand
== null ||
1223 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1225 return sampledValueTemplates
[index
]
1228 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1229 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1230 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1231 throw new BaseError(errorMsg
)
1234 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1238 const buildSampledValue
= (
1239 sampledValueTemplate
: SampledValueTemplate
,
1241 context
?: MeterValueContext
,
1242 phase
?: MeterValuePhase
1243 ): SampledValue
=> {
1244 const sampledValueContext
= context
?? sampledValueTemplate
.context
1245 const sampledValueLocation
=
1246 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1247 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1248 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1250 ...(sampledValueTemplate
.unit
!= null && {
1251 unit
: sampledValueTemplate
.unit
1253 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1254 ...(sampledValueTemplate
.measurand
!= null && {
1255 measurand
: sampledValueTemplate
.measurand
1257 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1258 ...{ value
: value
.toString() },
1259 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1260 } satisfies SampledValue
1263 const getMeasurandDefaultLocation
= (
1264 measurandType
: MeterValueMeasurand
1265 ): MeterValueLocation
| undefined => {
1266 switch (measurandType
) {
1267 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1268 return MeterValueLocation
.EV
1272 // const getMeasurandDefaultUnit = (
1273 // measurandType: MeterValueMeasurand
1274 // ): MeterValueUnit | undefined => {
1275 // switch (measurandType) {
1276 // case MeterValueMeasurand.CURRENT_EXPORT:
1277 // case MeterValueMeasurand.CURRENT_IMPORT:
1278 // case MeterValueMeasurand.CURRENT_OFFERED:
1279 // return MeterValueUnit.AMP
1280 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1281 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1282 // return MeterValueUnit.WATT_HOUR
1283 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1284 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1285 // case MeterValueMeasurand.POWER_OFFERED:
1286 // return MeterValueUnit.WATT
1287 // case MeterValueMeasurand.STATE_OF_CHARGE:
1288 // return MeterValueUnit.PERCENT
1289 // case MeterValueMeasurand.VOLTAGE:
1290 // return MeterValueUnit.VOLT
1294 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1295 export class OCPPServiceUtils
{
1296 public static readonly sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1297 public static readonly isIdTagAuthorized
= isIdTagAuthorized
1298 public static readonly buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1299 protected static getSampledValueTemplate
= getSampledValueTemplate
1300 protected static buildSampledValue
= buildSampledValue
1302 protected constructor () {
1303 // This is intentional
1306 public static isRequestCommandSupported (
1307 chargingStation
: ChargingStation
,
1308 command
: RequestCommand
1310 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1313 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1318 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1320 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1322 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1326 public static isIncomingRequestCommandSupported (
1327 chargingStation
: ChargingStation
,
1328 command
: IncomingRequestCommand
1330 const isIncomingRequestCommand
=
1331 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1333 isIncomingRequestCommand
&&
1334 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1338 isIncomingRequestCommand
&&
1339 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1341 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1343 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1347 public static isMessageTriggerSupported (
1348 chargingStation
: ChargingStation
,
1349 messageTrigger
: MessageTrigger
1351 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1352 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1356 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1358 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1361 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1366 public static isConnectorIdValid (
1367 chargingStation
: ChargingStation
,
1368 ocppCommand
: IncomingRequestCommand
,
1371 if (connectorId
< 0) {
1373 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1380 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1381 relativePath
: string,
1382 ocppVersion
: OCPPVersion
,
1383 moduleName
?: string,
1385 ): JSONSchemaType
<T
> {
1386 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1388 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1390 handleFileException(
1392 FileType
.JsonSchema
,
1393 error
as NodeJS
.ErrnoException
,
1394 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1395 { throwError
: false }
1397 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1398 return {} as JSONSchemaType
<T
>
1402 private static readonly logPrefix
= (
1403 ocppVersion
: OCPPVersion
,
1404 moduleName
?: string,
1408 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1409 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1410 : ` OCPP ${ocppVersion} |`
1411 return logPrefix(logMsg
)