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 connectorMaximumAvailablePower
: number | undefined
309 let socSampledValueTemplate
: SampledValueTemplate
| undefined
310 let voltageSampledValueTemplate
: SampledValueTemplate
| undefined
311 let powerSampledValueTemplate
: SampledValueTemplate
| undefined
312 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
313 let currentSampledValueTemplate
: SampledValueTemplate
| undefined
314 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {}
315 let energySampledValueTemplate
: SampledValueTemplate
| undefined
316 switch (chargingStation
.stationInfo
?.ocppVersion
) {
317 case OCPPVersion
.VERSION_16
:
319 timestamp
: new Date(),
323 socSampledValueTemplate
= getSampledValueTemplate(
326 MeterValueMeasurand
.STATE_OF_CHARGE
328 if (socSampledValueTemplate
!= null) {
329 const socMaximumValue
= 100
330 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0
331 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
332 ? getRandomFloatFluctuatedRounded(
333 Number.parseInt(socSampledValueTemplate
.value
),
334 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
336 : randomInt(socMinimumValue
, socMaximumValue
)
337 meterValue
.sampledValue
.push(
338 buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
340 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
342 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
343 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
347 `${chargingStation.logPrefix()} MeterValues measurand ${
348 meterValue.sampledValue[sampledValuesIndex].measurand ??
349 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
350 }: connector id ${connectorId}, transaction id ${
351 connector?.transactionId
352 }, value: ${socMinimumValue}/${
353 meterValue.sampledValue[sampledValuesIndex].value
354 }/${socMaximumValue}`
359 voltageSampledValueTemplate
= getSampledValueTemplate(
362 MeterValueMeasurand
.VOLTAGE
364 if (voltageSampledValueTemplate
!= null) {
365 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
366 ? Number.parseInt(voltageSampledValueTemplate
.value
)
367 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
368 chargingStation
.stationInfo
.voltageOut
!
369 const fluctuationPercent
=
370 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
371 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
372 voltageSampledValueTemplateValue
,
376 chargingStation
.getNumberOfPhases() !== 3 ||
377 (chargingStation
.getNumberOfPhases() === 3 &&
378 chargingStation
.stationInfo
.mainVoltageMeterValues
=== true)
380 meterValue
.sampledValue
.push(
381 buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
386 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
389 const phaseLineToNeutralValue
= `L${phase}-N`
390 const voltagePhaseLineToNeutralSampledValueTemplate
= getSampledValueTemplate(
393 MeterValueMeasurand
.VOLTAGE
,
394 phaseLineToNeutralValue
as MeterValuePhase
396 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined
397 if (voltagePhaseLineToNeutralSampledValueTemplate
!= null) {
398 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
399 voltagePhaseLineToNeutralSampledValueTemplate
.value
401 ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
402 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
403 chargingStation
.stationInfo
.voltageOut
!
404 const fluctuationPhaseToNeutralPercent
=
405 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
406 Constants
.DEFAULT_FLUCTUATION_PERCENT
407 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
408 voltagePhaseLineToNeutralSampledValueTemplateValue
,
409 fluctuationPhaseToNeutralPercent
412 meterValue
.sampledValue
.push(
414 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
415 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
417 phaseLineToNeutralValue
as MeterValuePhase
420 if (chargingStation
.stationInfo
.phaseLineToLineVoltageMeterValues
=== true) {
421 const phaseLineToLineValue
= `L${phase}-L${
422 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
423 ? (phase + 1) % chargingStation.getNumberOfPhases()
424 : chargingStation.getNumberOfPhases()
426 const voltagePhaseLineToLineValueRounded
= roundTo(
427 Math.sqrt(chargingStation
.getNumberOfPhases()) *
428 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
429 chargingStation
.stationInfo
.voltageOut
!,
432 const voltagePhaseLineToLineSampledValueTemplate
= getSampledValueTemplate(
435 MeterValueMeasurand
.VOLTAGE
,
436 phaseLineToLineValue
as MeterValuePhase
438 let voltagePhaseLineToLineMeasurandValue
: number | undefined
439 if (voltagePhaseLineToLineSampledValueTemplate
!= null) {
440 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
441 voltagePhaseLineToLineSampledValueTemplate
.value
443 ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
444 : voltagePhaseLineToLineValueRounded
445 const fluctuationPhaseLineToLinePercent
=
446 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
447 Constants
.DEFAULT_FLUCTUATION_PERCENT
448 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
449 voltagePhaseLineToLineSampledValueTemplateValue
,
450 fluctuationPhaseLineToLinePercent
453 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
454 voltagePhaseLineToLineValueRounded
,
457 meterValue
.sampledValue
.push(
459 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
460 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
462 phaseLineToLineValue
as MeterValuePhase
468 // Power.Active.Import measurand
469 powerSampledValueTemplate
= getSampledValueTemplate(
472 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
474 if (chargingStation
.getNumberOfPhases() === 3) {
475 powerPerPhaseSampledValueTemplates
= {
476 L1
: getSampledValueTemplate(
479 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
482 L2
: getSampledValueTemplate(
485 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
488 L3
: getSampledValueTemplate(
491 MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
496 if (powerSampledValueTemplate
!= null) {
497 checkMeasurandPowerDivider(chargingStation
, powerSampledValueTemplate
.measurand
)
498 const errMsg
= `MeterValues measurand ${
499 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
500 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
501 chargingStation.templateFile
502 }, cannot calculate ${
503 powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
505 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
506 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
507 const unitDivider
= powerSampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1
508 connectorMaximumAvailablePower
=
509 chargingStation
.getConnectorMaximumAvailablePower(connectorId
)
510 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
)
511 const connectorMaximumPowerPerPhase
= Math.round(
512 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
514 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0)
515 const connectorMinimumPowerPerPhase
= Math.round(
516 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
518 switch (chargingStation
.stationInfo
.currentOutType
) {
520 if (chargingStation
.getNumberOfPhases() === 3) {
521 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(
522 powerSampledValueTemplate
.value
524 ? getRandomFloatFluctuatedRounded(
525 getLimitFromSampledValueTemplateCustomValue(
526 powerSampledValueTemplate
.value
,
527 connectorMaximumPower
/ unitDivider
,
528 connectorMinimumPower
/ unitDivider
,
531 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
532 fallbackValue
: connectorMinimumPower
/ unitDivider
534 ) / chargingStation
.getNumberOfPhases(),
535 powerSampledValueTemplate
.fluctuationPercent
??
536 Constants
.DEFAULT_FLUCTUATION_PERCENT
539 const phase1FluctuatedValue
= isNotEmptyString(
540 powerPerPhaseSampledValueTemplates
.L1
?.value
542 ? getRandomFloatFluctuatedRounded(
543 getLimitFromSampledValueTemplateCustomValue(
544 powerPerPhaseSampledValueTemplates
.L1
.value
,
545 connectorMaximumPowerPerPhase
/ unitDivider
,
546 connectorMinimumPowerPerPhase
/ unitDivider
,
549 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
550 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
553 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
554 Constants
.DEFAULT_FLUCTUATION_PERCENT
557 const phase2FluctuatedValue
= isNotEmptyString(
558 powerPerPhaseSampledValueTemplates
.L2
?.value
560 ? getRandomFloatFluctuatedRounded(
561 getLimitFromSampledValueTemplateCustomValue(
562 powerPerPhaseSampledValueTemplates
.L2
.value
,
563 connectorMaximumPowerPerPhase
/ unitDivider
,
564 connectorMinimumPowerPerPhase
/ unitDivider
,
567 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
568 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
571 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
572 Constants
.DEFAULT_FLUCTUATION_PERCENT
575 const phase3FluctuatedValue
= isNotEmptyString(
576 powerPerPhaseSampledValueTemplates
.L3
?.value
578 ? getRandomFloatFluctuatedRounded(
579 getLimitFromSampledValueTemplateCustomValue(
580 powerPerPhaseSampledValueTemplates
.L3
.value
,
581 connectorMaximumPowerPerPhase
/ unitDivider
,
582 connectorMinimumPowerPerPhase
/ unitDivider
,
585 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
586 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
589 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
590 Constants
.DEFAULT_FLUCTUATION_PERCENT
593 powerMeasurandValues
.L1
=
594 phase1FluctuatedValue
??
595 defaultFluctuatedPowerPerPhase
??
596 getRandomFloatRounded(
597 connectorMaximumPowerPerPhase
/ unitDivider
,
598 connectorMinimumPowerPerPhase
/ unitDivider
600 powerMeasurandValues
.L2
=
601 phase2FluctuatedValue
??
602 defaultFluctuatedPowerPerPhase
??
603 getRandomFloatRounded(
604 connectorMaximumPowerPerPhase
/ unitDivider
,
605 connectorMinimumPowerPerPhase
/ unitDivider
607 powerMeasurandValues
.L3
=
608 phase3FluctuatedValue
??
609 defaultFluctuatedPowerPerPhase
??
610 getRandomFloatRounded(
611 connectorMaximumPowerPerPhase
/ unitDivider
,
612 connectorMinimumPowerPerPhase
/ unitDivider
615 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
616 ? getRandomFloatFluctuatedRounded(
617 getLimitFromSampledValueTemplateCustomValue(
618 powerSampledValueTemplate
.value
,
619 connectorMaximumPower
/ unitDivider
,
620 connectorMinimumPower
/ unitDivider
,
623 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
624 fallbackValue
: connectorMinimumPower
/ unitDivider
627 powerSampledValueTemplate
.fluctuationPercent
??
628 Constants
.DEFAULT_FLUCTUATION_PERCENT
630 : getRandomFloatRounded(
631 connectorMaximumPower
/ unitDivider
,
632 connectorMinimumPower
/ unitDivider
634 powerMeasurandValues
.L2
= 0
635 powerMeasurandValues
.L3
= 0
637 powerMeasurandValues
.allPhases
= roundTo(
638 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
643 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
644 ? getRandomFloatFluctuatedRounded(
645 getLimitFromSampledValueTemplateCustomValue(
646 powerSampledValueTemplate
.value
,
647 connectorMaximumPower
/ unitDivider
,
648 connectorMinimumPower
/ unitDivider
,
651 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
652 fallbackValue
: connectorMinimumPower
/ unitDivider
655 powerSampledValueTemplate
.fluctuationPercent
??
656 Constants
.DEFAULT_FLUCTUATION_PERCENT
658 : getRandomFloatRounded(
659 connectorMaximumPower
/ unitDivider
,
660 connectorMinimumPower
/ unitDivider
664 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
665 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
667 meterValue
.sampledValue
.push(
668 buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
)
670 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
671 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2)
672 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2)
674 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
675 connectorMaximumPowerRounded
||
676 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
677 connectorMinimumPowerRounded
||
681 `${chargingStation.logPrefix()} MeterValues measurand ${
682 meterValue.sampledValue[sampledValuesIndex].measurand ??
683 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
684 }: connector id ${connectorId}, transaction id ${
685 connector?.transactionId
686 }, value: ${connectorMinimumPowerRounded}/${
687 meterValue.sampledValue[sampledValuesIndex].value
688 }/${connectorMaximumPowerRounded}`
693 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
696 const phaseValue
= `L${phase}-N`
697 meterValue
.sampledValue
.push(
699 powerPerPhaseSampledValueTemplates
[
700 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
701 ] ?? powerSampledValueTemplate
,
702 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
704 phaseValue
as MeterValuePhase
707 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
708 const connectorMaximumPowerPerPhaseRounded
= roundTo(
709 connectorMaximumPowerPerPhase
/ unitDivider
,
712 const connectorMinimumPowerPerPhaseRounded
= roundTo(
713 connectorMinimumPowerPerPhase
/ unitDivider
,
717 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
718 connectorMaximumPowerPerPhaseRounded
||
719 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
720 connectorMinimumPowerPerPhaseRounded
||
724 `${chargingStation.logPrefix()} MeterValues measurand ${
725 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
726 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
728 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
729 }, connector id ${connectorId}, transaction id ${
730 connector?.transactionId
731 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
732 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
733 }/${connectorMaximumPowerPerPhaseRounded}`
738 // Current.Import measurand
739 currentSampledValueTemplate
= getSampledValueTemplate(
742 MeterValueMeasurand
.CURRENT_IMPORT
744 if (chargingStation
.getNumberOfPhases() === 3) {
745 currentPerPhaseSampledValueTemplates
= {
746 L1
: getSampledValueTemplate(
749 MeterValueMeasurand
.CURRENT_IMPORT
,
752 L2
: getSampledValueTemplate(
755 MeterValueMeasurand
.CURRENT_IMPORT
,
758 L3
: getSampledValueTemplate(
761 MeterValueMeasurand
.CURRENT_IMPORT
,
766 if (currentSampledValueTemplate
!= null) {
767 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
768 checkMeasurandPowerDivider(chargingStation
, currentSampledValueTemplate
.measurand
)
769 const errMsg
= `MeterValues measurand ${
770 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
771 }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
772 chargingStation.templateFile
773 }, cannot calculate ${
774 currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
776 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
777 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
778 connectorMaximumAvailablePower
== null &&
779 (connectorMaximumAvailablePower
=
780 chargingStation
.getConnectorMaximumAvailablePower(connectorId
))
781 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0
782 let connectorMaximumAmperage
: number
783 switch (chargingStation
.stationInfo
.currentOutType
) {
785 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
786 chargingStation
.getNumberOfPhases(),
787 connectorMaximumAvailablePower
,
788 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
789 chargingStation
.stationInfo
.voltageOut
!
791 if (chargingStation
.getNumberOfPhases() === 3) {
792 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
793 currentSampledValueTemplate
.value
795 ? getRandomFloatFluctuatedRounded(
796 getLimitFromSampledValueTemplateCustomValue(
797 currentSampledValueTemplate
.value
,
798 connectorMaximumAmperage
,
799 connectorMinimumAmperage
,
802 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
803 fallbackValue
: connectorMinimumAmperage
806 currentSampledValueTemplate
.fluctuationPercent
??
807 Constants
.DEFAULT_FLUCTUATION_PERCENT
810 const phase1FluctuatedValue
= isNotEmptyString(
811 currentPerPhaseSampledValueTemplates
.L1
?.value
813 ? getRandomFloatFluctuatedRounded(
814 getLimitFromSampledValueTemplateCustomValue(
815 currentPerPhaseSampledValueTemplates
.L1
.value
,
816 connectorMaximumAmperage
,
817 connectorMinimumAmperage
,
820 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
821 fallbackValue
: connectorMinimumAmperage
824 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
825 Constants
.DEFAULT_FLUCTUATION_PERCENT
828 const phase2FluctuatedValue
= isNotEmptyString(
829 currentPerPhaseSampledValueTemplates
.L2
?.value
831 ? getRandomFloatFluctuatedRounded(
832 getLimitFromSampledValueTemplateCustomValue(
833 currentPerPhaseSampledValueTemplates
.L2
.value
,
834 connectorMaximumAmperage
,
835 connectorMinimumAmperage
,
838 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
839 fallbackValue
: connectorMinimumAmperage
842 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
843 Constants
.DEFAULT_FLUCTUATION_PERCENT
846 const phase3FluctuatedValue
= isNotEmptyString(
847 currentPerPhaseSampledValueTemplates
.L3
?.value
849 ? getRandomFloatFluctuatedRounded(
850 getLimitFromSampledValueTemplateCustomValue(
851 currentPerPhaseSampledValueTemplates
.L3
.value
,
852 connectorMaximumAmperage
,
853 connectorMinimumAmperage
,
856 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
857 fallbackValue
: connectorMinimumAmperage
860 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
861 Constants
.DEFAULT_FLUCTUATION_PERCENT
864 currentMeasurandValues
.L1
=
865 phase1FluctuatedValue
??
866 defaultFluctuatedAmperagePerPhase
??
867 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
868 currentMeasurandValues
.L2
=
869 phase2FluctuatedValue
??
870 defaultFluctuatedAmperagePerPhase
??
871 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
872 currentMeasurandValues
.L3
=
873 phase3FluctuatedValue
??
874 defaultFluctuatedAmperagePerPhase
??
875 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
877 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
878 ? getRandomFloatFluctuatedRounded(
879 getLimitFromSampledValueTemplateCustomValue(
880 currentSampledValueTemplate
.value
,
881 connectorMaximumAmperage
,
882 connectorMinimumAmperage
,
885 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
886 fallbackValue
: connectorMinimumAmperage
889 currentSampledValueTemplate
.fluctuationPercent
??
890 Constants
.DEFAULT_FLUCTUATION_PERCENT
892 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
893 currentMeasurandValues
.L2
= 0
894 currentMeasurandValues
.L3
= 0
896 currentMeasurandValues
.allPhases
= roundTo(
897 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
898 chargingStation
.getNumberOfPhases(),
903 connectorMaximumAmperage
= DCElectricUtils
.amperage(
904 connectorMaximumAvailablePower
,
905 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
906 chargingStation
.stationInfo
.voltageOut
!
908 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
909 ? getRandomFloatFluctuatedRounded(
910 getLimitFromSampledValueTemplateCustomValue(
911 currentSampledValueTemplate
.value
,
912 connectorMaximumAmperage
,
913 connectorMinimumAmperage
,
916 chargingStation
.stationInfo
.customValueLimitationMeterValues
,
917 fallbackValue
: connectorMinimumAmperage
920 currentSampledValueTemplate
.fluctuationPercent
??
921 Constants
.DEFAULT_FLUCTUATION_PERCENT
923 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
)
926 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
927 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
929 meterValue
.sampledValue
.push(
930 buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
)
932 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
934 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
935 connectorMaximumAmperage
||
936 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
937 connectorMinimumAmperage
||
941 `${chargingStation.logPrefix()} MeterValues measurand ${
942 meterValue.sampledValue[sampledValuesIndex].measurand ??
943 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
944 }: connector id ${connectorId}, transaction id ${
945 connector?.transactionId
946 }, value: ${connectorMinimumAmperage}/${
947 meterValue.sampledValue[sampledValuesIndex].value
948 }/${connectorMaximumAmperage}`
953 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
956 const phaseValue
= `L${phase}`
957 meterValue
.sampledValue
.push(
959 currentPerPhaseSampledValueTemplates
[
960 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
961 ] ?? currentSampledValueTemplate
,
962 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
964 phaseValue
as MeterValuePhase
967 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1
969 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
970 connectorMaximumAmperage
||
971 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
972 connectorMinimumAmperage
||
976 `${chargingStation.logPrefix()} MeterValues measurand ${
977 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
978 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
980 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
981 }, connector id ${connectorId}, transaction id ${
982 connector?.transactionId
983 }, value: ${connectorMinimumAmperage}/${
984 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
985 }/${connectorMaximumAmperage}`
990 // Energy.Active.Import.Register measurand (default)
991 energySampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
992 if (energySampledValueTemplate
!= null) {
993 checkMeasurandPowerDivider(chargingStation
, energySampledValueTemplate
.measurand
)
995 energySampledValueTemplate
.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
996 connectorMaximumAvailablePower
== null &&
997 (connectorMaximumAvailablePower
=
998 chargingStation
.getConnectorMaximumAvailablePower(connectorId
))
999 const connectorMaximumEnergyRounded
= roundTo(
1000 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
1003 const connectorMinimumEnergyRounded
= roundTo(
1004 energySampledValueTemplate
.minimumValue
?? 0,
1007 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
1008 ? getRandomFloatFluctuatedRounded(
1009 getLimitFromSampledValueTemplateCustomValue(
1010 energySampledValueTemplate
.value
,
1011 connectorMaximumEnergyRounded
,
1012 connectorMinimumEnergyRounded
,
1014 limitationEnabled
: chargingStation
.stationInfo
.customValueLimitationMeterValues
,
1015 fallbackValue
: connectorMinimumEnergyRounded
,
1016 unitMultiplier
: unitDivider
1019 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
1021 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
)
1022 // Persist previous value on connector
1023 if (connector
!= null) {
1025 connector
.energyActiveImportRegisterValue
!= null &&
1026 connector
.energyActiveImportRegisterValue
>= 0 &&
1027 connector
.transactionEnergyActiveImportRegisterValue
!= null &&
1028 connector
.transactionEnergyActiveImportRegisterValue
>= 0
1030 connector
.energyActiveImportRegisterValue
+= energyValueRounded
1031 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
1033 connector
.energyActiveImportRegisterValue
= 0
1034 connector
.transactionEnergyActiveImportRegisterValue
= 0
1037 meterValue
.sampledValue
.push(
1039 energySampledValueTemplate
,
1041 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
1047 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1
1049 energyValueRounded
> connectorMaximumEnergyRounded
||
1050 energyValueRounded
< connectorMinimumEnergyRounded
||
1054 `${chargingStation.logPrefix()} MeterValues measurand ${
1055 meterValue.sampledValue[sampledValuesIndex].measurand ??
1056 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1057 }: connector id ${connectorId}, transaction id ${
1058 connector?.transactionId
1059 }, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
1064 case OCPPVersion
.VERSION_20
:
1065 case OCPPVersion
.VERSION_201
:
1067 throw new BaseError(
1068 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1073 export const buildTransactionEndMeterValue
= (
1074 chargingStation
: ChargingStation
,
1075 connectorId
: number,
1076 meterStop
: number | undefined
1078 let meterValue
: MeterValue
1079 let sampledValueTemplate
: SampledValueTemplate
| undefined
1080 let unitDivider
: number
1081 switch (chargingStation
.stationInfo
?.ocppVersion
) {
1082 case OCPPVersion
.VERSION_16
:
1084 timestamp
: new Date(),
1087 // Energy.Active.Import.Register measurand (default)
1088 sampledValueTemplate
= getSampledValueTemplate(chargingStation
, connectorId
)
1089 unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1
1090 meterValue
.sampledValue
.push(
1092 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1093 sampledValueTemplate
!,
1094 roundTo((meterStop
?? 0) / unitDivider
, 4),
1095 MeterValueContext
.TRANSACTION_END
1099 case OCPPVersion
.VERSION_20
:
1100 case OCPPVersion
.VERSION_201
:
1102 throw new BaseError(
1103 `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
1108 const checkMeasurandPowerDivider
= (
1109 chargingStation
: ChargingStation
,
1110 measurandType
: MeterValueMeasurand
| undefined
1112 if (chargingStation
.powerDivider
== null) {
1113 const errMsg
= `MeterValues measurand ${
1114 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1115 }: powerDivider is undefined`
1116 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1117 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1118 } else if (chargingStation
.powerDivider
<= 0) {
1119 const errMsg
= `MeterValues measurand ${
1120 measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1121 }: powerDivider have zero or below value ${chargingStation.powerDivider}`
1122 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`)
1123 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, RequestCommand
.METER_VALUES
)
1127 const getLimitFromSampledValueTemplateCustomValue
= (
1128 value
: string | undefined,
1132 limitationEnabled
?: boolean
1133 fallbackValue
?: number
1134 unitMultiplier
?: number
1139 limitationEnabled
: false,
1145 const parsedValue
= Number.parseInt(value
?? '')
1146 if (options
.limitationEnabled
=== true) {
1149 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1150 (!isNaN(parsedValue
) ? parsedValue
: Number.POSITIVE_INFINITY
) * options
.unitMultiplier
!,
1156 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1157 return (!isNaN(parsedValue
) ? parsedValue
: options
.fallbackValue
!) * options
.unitMultiplier
!
1160 const getSampledValueTemplate
= (
1161 chargingStation
: ChargingStation
,
1162 connectorId
: number,
1163 measurand
: MeterValueMeasurand
= MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
,
1164 phase
?: MeterValuePhase
1165 ): SampledValueTemplate
| undefined => {
1166 const onPhaseStr
= phase
!= null ? `on phase ${phase} ` : ''
1167 if (!OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(measurand
)) {
1169 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1174 measurand
!== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1175 getConfigurationKey(
1177 StandardParametersKey
.MeterValuesSampledData
1178 )?.value
?.includes(measurand
) === false
1181 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
1182 StandardParametersKey.MeterValuesSampledData
1187 const sampledValueTemplates
=
1188 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1189 chargingStation
.getConnectorStatus(connectorId
)!.MeterValues
1192 isNotEmptyArray(sampledValueTemplates
) && index
< sampledValueTemplates
.length
;
1196 !OCPPConstants
.OCPP_MEASURANDS_SUPPORTED
.includes(
1197 sampledValueTemplates
[index
]?.measurand
?? MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
1201 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1205 sampledValueTemplates
[index
]?.phase
=== phase
&&
1206 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1207 getConfigurationKey(
1209 StandardParametersKey
.MeterValuesSampledData
1210 )?.value
?.includes(measurand
) === true
1212 return sampledValueTemplates
[index
]
1215 sampledValueTemplates
[index
]?.phase
== null &&
1216 sampledValueTemplates
[index
]?.measurand
=== measurand
&&
1217 getConfigurationKey(
1219 StandardParametersKey
.MeterValuesSampledData
1220 )?.value
?.includes(measurand
) === true
1222 return sampledValueTemplates
[index
]
1224 measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
&&
1225 (sampledValueTemplates
[index
]?.measurand
== null ||
1226 sampledValueTemplates
[index
]?.measurand
=== measurand
)
1228 return sampledValueTemplates
[index
]
1231 if (measurand
=== MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
) {
1232 const errorMsg
= `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
1233 logger
.error(`${chargingStation.logPrefix()} ${errorMsg}`)
1234 throw new BaseError(errorMsg
)
1237 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
1241 const buildSampledValue
= (
1242 sampledValueTemplate
: SampledValueTemplate
,
1244 context
?: MeterValueContext
,
1245 phase
?: MeterValuePhase
1246 ): SampledValue
=> {
1247 const sampledValueContext
= context
?? sampledValueTemplate
.context
1248 const sampledValueLocation
=
1249 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1250 sampledValueTemplate
.location
?? getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!)
1251 const sampledValuePhase
= phase
?? sampledValueTemplate
.phase
1253 ...(sampledValueTemplate
.unit
!= null && {
1254 unit
: sampledValueTemplate
.unit
1256 ...(sampledValueContext
!= null && { context
: sampledValueContext
}),
1257 ...(sampledValueTemplate
.measurand
!= null && {
1258 measurand
: sampledValueTemplate
.measurand
1260 ...(sampledValueLocation
!= null && { location
: sampledValueLocation
}),
1261 ...{ value
: value
.toString() },
1262 ...(sampledValuePhase
!= null && { phase
: sampledValuePhase
})
1263 } satisfies SampledValue
1266 const getMeasurandDefaultLocation
= (
1267 measurandType
: MeterValueMeasurand
1268 ): MeterValueLocation
| undefined => {
1269 switch (measurandType
) {
1270 case MeterValueMeasurand
.STATE_OF_CHARGE
:
1271 return MeterValueLocation
.EV
1275 // const getMeasurandDefaultUnit = (
1276 // measurandType: MeterValueMeasurand
1277 // ): MeterValueUnit | undefined => {
1278 // switch (measurandType) {
1279 // case MeterValueMeasurand.CURRENT_EXPORT:
1280 // case MeterValueMeasurand.CURRENT_IMPORT:
1281 // case MeterValueMeasurand.CURRENT_OFFERED:
1282 // return MeterValueUnit.AMP
1283 // case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1284 // case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1285 // return MeterValueUnit.WATT_HOUR
1286 // case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1287 // case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1288 // case MeterValueMeasurand.POWER_OFFERED:
1289 // return MeterValueUnit.WATT
1290 // case MeterValueMeasurand.STATE_OF_CHARGE:
1291 // return MeterValueUnit.PERCENT
1292 // case MeterValueMeasurand.VOLTAGE:
1293 // return MeterValueUnit.VOLT
1297 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
1298 export class OCPPServiceUtils
{
1299 public static readonly sendAndSetConnectorStatus
= sendAndSetConnectorStatus
1300 public static readonly restoreConnectorStatus
= restoreConnectorStatus
1301 public static readonly isIdTagAuthorized
= isIdTagAuthorized
1302 public static readonly buildTransactionEndMeterValue
= buildTransactionEndMeterValue
1303 protected static getSampledValueTemplate
= getSampledValueTemplate
1304 protected static buildSampledValue
= buildSampledValue
1306 protected constructor () {
1307 // This is intentional
1310 public static isRequestCommandSupported (
1311 chargingStation
: ChargingStation
,
1312 command
: RequestCommand
1314 const isRequestCommand
= Object.values
<RequestCommand
>(RequestCommand
).includes(command
)
1317 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
== null
1322 chargingStation
.stationInfo
?.commandsSupport
?.outgoingCommands
?.[command
] != null
1324 return chargingStation
.stationInfo
.commandsSupport
.outgoingCommands
[command
]
1326 logger
.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
1330 public static isIncomingRequestCommandSupported (
1331 chargingStation
: ChargingStation
,
1332 command
: IncomingRequestCommand
1334 const isIncomingRequestCommand
=
1335 Object.values
<IncomingRequestCommand
>(IncomingRequestCommand
).includes(command
)
1337 isIncomingRequestCommand
&&
1338 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
== null
1342 isIncomingRequestCommand
&&
1343 chargingStation
.stationInfo
?.commandsSupport
?.incomingCommands
[command
] != null
1345 return chargingStation
.stationInfo
.commandsSupport
.incomingCommands
[command
]
1347 logger
.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
1351 public static isMessageTriggerSupported (
1352 chargingStation
: ChargingStation
,
1353 messageTrigger
: MessageTrigger
1355 const isMessageTrigger
= Object.values(MessageTrigger
).includes(messageTrigger
)
1356 if (isMessageTrigger
&& chargingStation
.stationInfo
?.messageTriggerSupport
== null) {
1360 chargingStation
.stationInfo
?.messageTriggerSupport
?.[messageTrigger
] != null
1362 return chargingStation
.stationInfo
.messageTriggerSupport
[messageTrigger
]
1365 `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
1370 public static isConnectorIdValid (
1371 chargingStation
: ChargingStation
,
1372 ocppCommand
: IncomingRequestCommand
,
1375 if (connectorId
< 0) {
1377 `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
1384 protected static parseJsonSchemaFile
<T
extends JsonType
>(
1385 relativePath
: string,
1386 ocppVersion
: OCPPVersion
,
1387 moduleName
?: string,
1389 ): JSONSchemaType
<T
> {
1390 const filePath
= join(dirname(fileURLToPath(import.meta
.url
)), relativePath
)
1392 return JSON
.parse(readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>
1394 handleFileException(
1396 FileType
.JsonSchema
,
1397 error
as NodeJS
.ErrnoException
,
1398 OCPPServiceUtils
.logPrefix(ocppVersion
, moduleName
, methodName
),
1399 { throwError
: false }
1401 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1402 return {} as JSONSchemaType
<T
>
1406 private static readonly logPrefix
= (
1407 ocppVersion
: OCPPVersion
,
1408 moduleName
?: string,
1412 isNotEmptyString(moduleName
) && isNotEmptyString(methodName
)
1413 ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
1414 : ` OCPP ${ocppVersion} |`
1415 return logPrefix(logMsg
)